取消订阅后调用事件

塞巴斯蒂安·舒曼(Sebastian Schumann)

我正在使用一个对象(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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

调用方法后的调用事件

来自分类Dev

调用方法后的调用事件

来自分类Dev

调用事件后等待事件侦听器完成

来自分类Dev

如何调用事件?

来自分类Dev

调用.one()函数后,如何启用事件处理程序?

来自分类Dev

环回:重置后未调用事件 resetPasswordRequest

来自分类Dev

是否可以通过单个方法调用取消订阅多个事件?

来自分类Dev

AngularJS:取消订阅事件

来自分类Dev

C#调用事件

来自分类Dev

从主函数调用事件

来自分类Dev

通过showInputDialog调用事件

来自分类Dev

绩效问题-取消订阅事件

来自分类Dev

在c#中使用按钮事件后,是否需要取消订阅按钮事件?

来自分类Dev

调用基础事件后Async.AwaitEvent不会取消

来自分类Dev

取消订阅后,我的订阅继续运行

来自分类Dev

单击更新按钮后,模态弹出窗口不调用事件

来自分类Dev

发射后取消订阅间隔

来自分类Dev

加载脚本后使用事件

来自分类Dev

调用事件,h(args)与EventName?.Invoke()

来自分类Dev

在Bootstrap面板上调用事件展开

来自分类Dev

用JavaScript创建和调用事件

来自分类Dev

一次通用事件调用?

来自分类Dev

检查哪个对象调用事件方法

来自分类Dev

直接调用事件处理程序

来自分类Dev

哪个UserControl调用事件MouseEnter?

来自分类Dev

如何获取调用事件的BrowserWindow实例?

来自分类Dev

在函数C#中调用事件

来自分类Dev

在UI上调用事件方法

来自分类Dev

检查哪个对象调用事件方法