取消订阅后调用事件

时间:2014-05-19 11:47:41

标签: c# multithreading events event-handling

我正在使用一个对象(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这只能使用第二个实现)。但我很失望。

1 个答案:

答案 0 :(得分:1)

如果你有两个线程 - 一个调用事件处理程序,一个取消订阅 - 总是是竞争条件,你最终得到以下顺序:

  • 赛事加注者开始举起活动
  • 事件取消订阅者取消订阅
  • 事件提升者执行处理程序

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

  • 赛事加注者开始举起活动
  • Event-raiser开始执行处理程序 - 但它还没有到达第一行用户代码
  • 事件取消订阅者取消订阅
  • 处理程序执行用户代码

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

您可以轻松地在处理程序中实现自己的“我真的打算在这一点上运行”的检查,但是您需要编写自己的语义。 可以使用锁定以确保处理程序执行时 时处理程序取消订阅不会发生,但这对我来说非常危险,除非您知道您的事件处理程序将在短时间内完成。