我正在使用一个对象(EventReceiver
)将一个成员注册到通过ctor导入的对象(EventSource
)的事件。 EventReceiver
实施IDisposable
并取消订阅EventSource
。
问题是有不同的线程调用事件处理程序并处理EventReceiver
。取消订阅完成后将调用该事件。在举办活动和取消订阅之间存在种族歧视。
怎么可以解决?
以下是演示此问题的示例实现:
internal class Program
{
private static void Main(string[] args)
{
var eventSource = new EventSource();
Task.Factory.StartNew(
() =>
{
while (true)
{
eventSource.RaiseEvent();
}
});
Task.Factory.StartNew(
() =>
{
while (true)
{
new EventReceiver(eventSource).Dispose();
}
});
Console.ReadKey();
}
}
public class EventSource
{
public event EventHandler<EventArgs> SampleEvent;
public void RaiseEvent()
{
var handler = this.SampleEvent;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
public class EventReceiver : IDisposable
{
private readonly EventSource _source;
public EventReceiver(EventSource source)
{
this._source = source;
this._source.SampleEvent += this.OnSampleEvent;
}
public bool IsDisposed { get; private set; }
private void OnSampleEvent(object sender, EventArgs args)
{
if (this.IsDisposed)
{
throw new InvalidOperationException("This should never happen...");
}
}
public void Dispose()
{
this._source.SampleEvent -= this.OnSampleEvent;
this.IsDisposed = true;
}
}
在多核处理器上启动程序后,异常将被抛出。是的我知道var handler = this.SampleEvent
将创建事件处理程序的副本并导致问题。
我试图像这样实现RaiseEvent
方法,但它没有帮助:
public void RaiseEvent()
{
try
{
this.SampleEvent(this, EventArgs.Empty);
}
catch (Exception)
{
}
}
问题是:如何以多线程方式实现线程安全注册和注销事件?
我的期望是取消注册将暂停,直到当前已解雇的事件结束(meybe这只能使用第二个实现)。但我很失望。
答案 0 :(得分:1)
如果你有两个线程 - 一个调用事件处理程序,一个取消订阅 - 总是是竞争条件,你最终得到以下顺序:
这在.NET中代表的不变性是不可避免的。即使使用某种线程安全的,可变的委托类型,总会有一个更细粒度的竞争条件:
更糟糕的是,您可以在处理程序正在执行时取消订阅 - 您期望会发生什么?您是否希望处理程序代码被任意终止?
您可以轻松地在处理程序中实现自己的“我真的打算在这一点上运行”的检查,但是您需要编写自己的语义。 可以使用锁定以确保处理程序执行时 时处理程序取消订阅不会发生,但这对我来说非常危险,除非您知道您的事件处理程序将在短时间内完成。