Anytime the beginner asks something like: How to update the GUI from another thread in C#?, the answer is pretty straight:
if (foo.InvokeRequired)
{
foo.BeginInvoke(...)
} else {
...
}
But is it really good to use it? Right after non-GUI thread executes foo.InvokeRequired
the state of foo
can change. For example, if we close form right after foo.InvokeRequired
, but before foo.BeginInvoke
, calling foo.BeginInvoke
will lead to InvalidOperationException
: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. This wouldn't happen if we close the form before calling InvokeRequired
, because it would be false
even when called from non-GUI thread.
Another example: Let's say foo
is a TextBox
. If you close form, and after that non-GUI thread executes foo.InvokeRequired
(which is false, because form is closed) and foo.AppendText
it will lead to ObjectDisposedException
.
In contrast, in my opinion using WindowsFormsSynchronizationContext
is much easier - posting callback by using Post
will occur only if thread still exists, and synchronous calls using Send
throws InvalidAsynchronousStateException
if thread not exists anymore.
Isn't using WindowsFormsSynchronizationContext
just easier? Am I missing something? Why should I use InvokeRequired-BeginInvoke pattern if it's not really thread safe? What do you think is better?
WindowsFormsSynchronizationContext
works by attaching itself to a special control that is bound to the thread where the context is created.
So
if (foo.InvokeRequired)
{
foo.BeginInvoke(...)
} else {
...
}
Can be replaced with a safer version :
context.Post(delegate
{
if (foo.IsDisposed) return;
...
});
Assuming that context
is a WindowsFormsSynchronizationContext
created on the same UI thread that foo
was.
This version avoid the problem you evoke :
Right after non-GUI thread executes foo.InvokeRequired the state of foo can change. For example, if we close form right after foo.InvokeRequired, but before foo.BeginInvoke, calling foo.BeginInvoke will lead to InvalidOperationException: Invoke or BeginInvoke cannot be called on a control until the window handle has been created. This wouldn't happen if we close the form before calling InvokeRequired, because it would be false even when called from non-GUI thread.
Beware of some special cases with WindowsFormsSynchronizationContext.Post
if you play with multiple message loops or multiple UI threads :
WindowsFormsSynchronizationContext.Post
will execute the delegate only if there still is a message pump on the thread where it was created. If there isn't nothing happens and no exception is raised.Application.Run
for example) the delegate will execute (It's due to the fact that the system maintain a message queue per thread without any knowledge about the fact that someone is pumping message from it or not)WindowsFormsSynchronizationContext.Send
will throw InvalidAsynchronousStateException
if the thread it's bound to isn't alive anymore. But if the thread it's bound to is alive and doesn't run a message loop it won't be executed immediately but will still be placed on the message queue and executed if Application.Run
is executed again.None of these cases should execute code unexpectedly if IsDisposed
is called on a control that is automatically disposed (Like the main form) as the delegate will immediately exit even if it's executed at an unexpected time.
The dangerous case is calling WindowsFormsSynchronizationContext.Send
and considering that the code will be executed: It might not, and there is now way to know if it did anything.
My conclusion would be that WindowsFormsSynchronizationContext
is a better solution as long as it's correctly used.
It can create sublte problems in complex cases but common GUI applications with one message loop that live as long as the application itself will always be fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With