将观察器模式与GUI一起使用时的竞争条件

本文关键字:竞争 条件 一起 观察 模式 GUI | 更新日期: 2023-09-27 18:21:51

我是C#&线程,我最近开始开发一个使用多线程的实用程序。我有一些事件处理逻辑是由一个线程完成的,然后是一个单独线程上的GUI,它观察事件处理程序并在接收到新事件时接收通知。

当用户手动关闭GUI时,我将其分离,这样它就不再观察事件处理程序。然而,下次事件处理程序接收到事件时,它会认为它的观察者列表中仍然有一些内容。我添加了一些打印输出/断点,它似乎进入了NotifyObserver并点击foreach循环,然后进入分离方法,清空观察者列表,然后当它返回到Notifyobserver时,它试图访问的观察者已经被释放,它得到了一个异常。

我在这个页面上看到,你应该使用锁来防止竞争条件的发生,我尝试在NotifyObserver中的foreach之前使用observer列表上的锁,但它仍然会得到异常。我想这可能与锁定有关,因为锁定无法阻止GUI在另一个线程上关闭,所以当我尝试锁定时,其他线程不会等待,但我是新手,所以我真的不确定。我也试着用这些方法扔了一堆其他的锁,但似乎没有任何效果。

我已经包含了下面涉及的3个方法的代码,Detach和NotifyObserver在我的事件处理程序中,HandleClosing在我的观察器中

    protected void HandleClosing(object sender, EventArgs e)
    {
        handler.Detach(this);
    }
    public void Detach(SubscriberObserver observer)
    {
        observers.Remove(observer);
    }
    public void NotifyObservers()
    {
        foreach (SubscriberObserver observer in observers)
        {
            observer.Invoke(new Action(() => { observer.Notify(); }));
        }
    }

将观察器模式与GUI一起使用时的竞争条件

我不知道你的观察者集合有什么类型,但我认为它是一个线程安全的集合,当通过foreach循环迭代它时,它可能会以以下方式表现。它锁定自己,然后创建自己的IEnumerable副本,然后解锁自己。然后,迭代从副本的元素开始。如果在创建副本后从集合中删除元素,则无所谓,循环仍会遇到已删除的元素。

要修复您的竞争条件,您需要在整个迭代中锁定集合,还应该在同一对象的锁内执行移除。您可以创建一个仅用于此目的的锁定对象,也可以锁定ICollection.SyncRoot(如果您的集合实现了这一点)。

如果是observer is Control == true,则在调用Invoke时可能会遇到死锁。请尝试调用BeginInvoke。MSDN中的一句话:"这两种方法的区别在于,调用Invoke是阻塞调用,而调用BeginInvoke不是。在大多数情况下,调用BeginInvoke更有效,因为辅助线程可以继续执行,而不必等待主UI线程完成更新用户界面的工作。"