事件在取消订阅后调用



我正在使用一个对象(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)
    {
    }
}

问题是:如何在多线程方式下实现线程安全的事件注册和取消注册?

我的期望是取消注册将被暂停,直到当前触发的事件已经完成(也许这只能使用第二个实现)。但我很失望。

如果您有两个线程—一个调用事件处理程序,另一个取消订阅—那么总是是一个竞争条件,您将以以下顺序结束:

  • event -raiser开始引发事件
  • Event-unsubscriber取消订阅
  • Event-raiser执行handler

在。net中,这是不可避免的委托的不变性。即使使用某种线程安全的可变委托类型,也总会有一个细粒度的竞争条件:

  • event -raiser开始引发事件
  • Event-raiser开始执行处理程序-但它还没有到达用户代码的第一行
  • Event-unsubscriber取消订阅
  • 处理程序执行用户代码

更糟的是,您可以在处理程序正在执行时取消订阅——您希望发生什么呢?您希望处理程序代码被任意终止吗?

您可以轻松地在处理程序中实现自己对"我是否真的要在此时运行"的检查,但您需要制定自己的语义。可以使用锁来确保在处理程序执行时处理程序取消订阅不会发生,但这对我来说感觉相当危险,除非您知道您的事件处理程序将在短时间内完成。

相关内容

  • 没有找到相关文章

最新更新