Состояние гонки при использовании шаблона наблюдателя с графическим интерфейсом - PullRequest
4 голосов
/ 31 января 2012

Я новичок в C # и многопоточности, и недавно я начал работать над утилитой, использующей несколько потоков.У меня есть некоторая логика обработки событий, выполняемая одним потоком, а затем графический интерфейс пользователя в отдельном потоке, который наблюдает за обработчиком событий и получает уведомления при получении новых событий.

Когда пользовательский интерфейс вручную закрывается пользователемЯ отсоединяю его, чтобы он больше не наблюдал за обработчиком событий.Однако в следующий раз, когда обработчик события получает событие, он думает, что в его списке наблюдателей все еще есть что-то.Я добавил несколько выводов / точек останова, и он, похоже, заходит в NotifyObservers и попадает в цикл foreach, затем переходит в метод detach, очищает список наблюдателей, а затем, когда он возвращается в NotifyObservers, наблюдатель, к которому он пытается получить доступ, уже удален.и он получает исключение.

На этой странице я видел, что вы должны использовать блокировки, чтобы не допустить возникновения условий гонки, и я попытался использовать один из них в списке наблюдателей перед началом foreach вNotifyObservers, и он все еще получает исключение.Я думаю, что это может быть связано с блокировкой, которая не может предотвратить закрытие графического интерфейса в другом потоке, поэтому другой поток не ждет, когда я пытаюсь заблокировать, но я новичок в этом, поэтому яне совсем уверен.Я также попытался использовать несколько других блокировок в этих методах, но ничего не дало никакого эффекта.

Я включил код для трех методов, описанных ниже, 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 голосов
/ 31 января 2012

Я не знаю, к какому типу относится ваша коллекция наблюдателей, но я предполагаю, что это своего рода потокобезопасная коллекция, которая может вести себя следующим образом при итерации по ней через цикл foreach. Он блокирует себя, затем создает собственную копию IEnumerable, а затем разблокирует себя. Затем итерация начинается с элементов копии. Если вы удалите элемент из коллекции после того, как копия будет создана, это не имеет значения, цикл все равно встретит удаленный элемент.

Чтобы исправить состояние вашей гонки, вам нужно заблокировать коллекцию на всю итерацию, а также выполнить удаление внутри блокировки на том же объекте. Вы можете создать объект блокировки для этой единственной цели или можете заблокировать ICollection.SyncRoot, если ваша коллекция реализует это.

Если observer is Control == true, возможно, вы зашли в тупик при вызове Invoke. Попробуйте позвонить BeginInvoke. Цитата из MSDN : «Разница между этими двумя методами заключается в том, что вызов Invoke является блокирующим, а вызов BeginInvoke - нет. В большинстве случаев более эффективно вызывать BeginInvoke, поскольку вторичный поток может продолжить выполнение, не дожидаясь, пока основной поток пользовательского интерфейса завершит свою работу, обновляя интерфейс пользователя. "

...