WindowsFormsSynchronizationContext
работает путем присоединения к специальному элементу управления, который связан с потоком, в котором создается контекст.
Итак
if (foo.InvokeRequired)
{
foo.BeginInvoke(...)
} else {
...
}
Можно заменить на более безопасную версию:
context.Post(delegate
{
if (foo.IsDisposed) return;
...
});
Предполагая, что context
- это WindowsFormsSynchronizationContext
, созданный в том же потоке пользовательского интерфейса, которым был foo
.
Эта версия позволяет избежать проблемы, которую вы вызываете:
Сразу после того, как поток не-GUI выполнит foo.InvokeRequired, состояние foo может измениться. Например, если мы закрываем форму сразу после foo.InvokeRequired, но перед foo.BeginInvoke, вызов foo.BeginInvoke приведет к InvalidOperationException: Invoke или BeginInvoke не могут быть вызваны для элемента управления, пока не будет создан дескриптор окна. Этого не произойдет, если мы закроем форму перед вызовом InvokeRequired, потому что она будет ложной даже при вызове из потока, не являющегося GUI.
Остерегайтесь некоторых особых случаев с WindowsFormsSynchronizationContext.Post
, если вы играете с несколькими циклами сообщений или несколькими потоками пользовательского интерфейса:
WindowsFormsSynchronizationContext.Post
выполнит делегат только в том случае, если в потоке, в котором он был создан, все еще есть насос сообщений. Если нет , то ничего не происходит и не возникает исключение .
Также, если к потоку позже присоединяется еще один насос сообщений (например, посредством второго вызова Application.Run
), делегат будет выполнен (Это связано с тем, что система поддерживает очередь сообщений для каждого потока, не зная о том, что кто-то отправляет сообщение из нее или нет)
WindowsFormsSynchronizationContext.Send
сгенерирует InvalidAsynchronousStateException
, если нить, с которой он связан, больше не жива. Но если поток, с которым он связан, является живым и не запускает цикл сообщений , он не будет выполнен немедленно , но все равно будет помещен в очередь сообщений и выполнен, если Application.Run
будет выполнен снова.
Ни в одном из этих случаев не должен выполняться код неожиданно, если IsDisposed
вызывается для элемента управления, который автоматически удаляется (как и основная форма), поскольку делегат немедленно завершает работу, даже если он выполняется в неожиданное время.
Опасный случай вызывает WindowsFormsSynchronizationContext.Send
и полагает, что код будет выполнен: он может и не быть, и теперь есть способ узнать, что он сделал.
Мой вывод: WindowsFormsSynchronizationContext
- лучшее решение, если оно правильно используется.
В сложных случаях это может создать проблемы с подлостью, но в обычных приложениях с графическим интерфейсом с одним циклом сообщений, которые живут до тех пор, пока само приложение всегда будет в порядке.