我正在使用一个对象(EventReceiver
),该成员将成员注册到EventSource
通过ctor导入的对象()的事件中。该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)
{
}
}
问题是:如何以多线程方式实现向事件注册和从事件注销的线程安全?
我的期望是取消注册将被挂起,直到当前触发的事件完成为止(也许这只能在第二种实现中起作用)。但是我很失望。
如果您有两个线程-一个线程调用事件处理程序,而一个取消订阅-则总会有一个竞争条件,最终您将按照以下顺序进行操作:
.NET中的代表是不变的,这是不可避免的。即使使用某种线程安全的,可变的委托类型,也总是存在更细粒度的竞争条件:
更糟的是,你可以退订,而处理程序执行-你会希望再发生?您希望处理程序代码被任意终止吗?
您可以在处理程序中轻松实现对“我真的打算在此时运行”的检查,但是您需要弄清楚自己的语义。您可以使用锁定,以确保不能发生处理退订,而处理程序执行,但感觉很危险的我,除非你知道你的事件处理程序将在很短的时间内完成。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句