使用带有GUI的观察者模式时的竞争条件

时间:2012-01-30 21:30:59

标签: c# winforms race-condition observer-pattern

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

当用户手动关闭GUI时,我将其分离,以便不再观察事件处理程序。但是,下次事件处理程序收到一个事件时,它认为它仍然有一些观察者列表。我添加了一些打印输出/断点,它似乎进入NotifyObservers并命中foreach循环,然后进入detach方法,清空观察者列表,然后当它返回NotifyObservers时,它尝试访问的观察者已经被处理掉了它有一个例外。

我在this page上看到你应该使用锁来防止竞争条件发生,我尝试在NotifyObservers中的foreach之前使用观察者列表中的一个,它仍然会得到异常。我认为它可能与锁定无法阻止GUI关闭另一个线程有关,所以当我试图锁定时其他线程不会等待,但我是新手,所以我是不太确定。我尝试在这些方法中抛出一堆其他锁,似乎没有任何效果。

我已经包含了下面涉及的3种方法的代码,Detach和NotifyObservers在我的事件处理程序中,而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(); }));
        }
    }

1 个答案:

答案 0 :(得分:0)

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

要修复竞争条件,您需要锁定整个迭代的集合,并且还应该在同一对象的锁内执行删除。您可以为此唯一目的创建一个锁定对象,或者如果您的集合实现了锁定对象,则可以锁定ICollection.SyncRoot

如果observer is Control == true在调用Invoke时可能遇到死锁。请尝试拨打BeginInvoke。来自MSDN的引用:“两种方法之间的区别在于调用Invoke是一个阻塞调用,而对BeginInvoke的调用则不是。在大多数情况下调用BeginInvoke更有效,因为辅助线程可以继续无需等待主UI线程完成更新用户界面的工作即可执行。“