То, что вы не учитываете, - то, что делегат, переданный Invoke
, вызывается асинхронно в потоке пользовательского интерфейса.Вызов Invoke
отправляет сообщение в очередь сообщений форм и через некоторое время принимается.
Что происходит не так:
UI Thread Background Thread
Call update()
take lock
Call Invoke()
Call update()
release lock
Call Dispose()
take lock
release lock
А вместо:
UI Thread Background Thread
Call update()
take lock
Call Invoke()
block until UI Thread processes the message
Process messages
...
Dispose()
wait for lock ****** Deadlock! *****
...
Call update()
release lock
Из-за этого фоновый поток может удерживать блокировку, пока поток пользовательского интерфейса пытается запустить Dispose
Решение гораздо проще, чем вы пытались.Поскольку Invoke публикуется асинхронно, блокировка не требуется.
private bool _disposed = false;
private void update(object sender, EventArgs e)
{
if (InvokeRequired)
{
EventHandler handler = new EventHandler(update);
Invoke(handler);
return;
}
if (_disposed) return;
label.Text = "blah";
}
protected override void Dispose(bool disposing)
{
eventfullObject.OnUpdate -= update;
_disposed = true; // this is never called
base.Dispose(disposing);
}
Флаг _disposed только читается или записывается в потоке пользовательского интерфейса, поэтому блокировка не требуется.Теперь вызов стека выглядит так:
UI Thread Background Thread
Call update()
take lock
Call Invoke()
block until UI Thread processes the message
Process messages
...
Dispose()
_disposed = true;
...
Call update()
_disposed is true so do nothing