Why is InvokeRequired preferred over WindowsFormsSynchronizationContext?
Asked Answered
W

2

7

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?

Without answered 9/3, 2011 at 12:48 Comment(0)
S
7

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.
    Also if another message pump is later attached to the thread (Via a second call to 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.

Sundew answered 9/3, 2011 at 13:13 Comment(12)
Yeah, but it calls BeginInvoke and Invoke on some magic controlToSendTo. See also my comment to Daniels answer.Without
I think that this magic controlToSendTo is created just to make sure it will exist and has created handle during the lifetime of the process, so InvalidOperationException will never occur in contrast to manually calling BeginInvoke on control, that can no longer has handle.Without
Yes i didn't see that it uses a spacial marshaling control. In this case it is useful for invoke, you should make a full answer with this info as it seem the biggest difference.Sundew
Edited my answer with the consequences of this.Sundew
I think that covers a lot. Although I think, that the gap between InvokeRequired and actually calling BeginInvoke and Invoke should be mentioned when answering to similar question. BTW, thanks for your Reflector thing - I totally forgot to use it earlier :)Without
In fact the problem is even worse in the standard sample code if the handle isn't created because the control was disposed... InvokeRequired return false and the method will proceed manipulating a control that was disposed... not always the best thing to do especially depending on where the potentially thrown exception will end up it might crash the process :DSundew
So if you uses WindowsFormsSynchronizationContext you risk being in a case where you call a delegate on an object that doesn't have any handle and was disposed long time ago - this is what Control.IsDisposed is for (msdn.microsoft.com/en-us/library/…).Fluor
@OhadSchneider the control you need to call this property on isn't exposed. It must be the control used as a reference. You can access it using reflection via Application.ThreadContext.FromCurrent().MarshalingControl or accessing WindowsFormsSynchronizationContext member controlToSendTo but that's a bit cumbersomeSundew
@JulienRoncaglia you don't need to check that control, it will be there (non-disposed) so long as there's a UI thread to run your delegate (it is basically its job to be there).Fluor
@OhadSchneider yeah I read your comment too fast.I fully re-read my original answer and don't understand it completely either... Control.isDisposed would work. I can't see the problem I was speaking about 5 years ago XD i'll edit itSundew
@OhadSchneider rewrote the answer with details on potential problems & changed conclusion.Sundew
@JulienRoncaglia much better - converted my downvote to upvote :) BTW I think that in the vast majority of cases, you want SynchronizationContext.Post (not Send) anyway...Fluor
F
1

Who said InvokeRequired / Control.BeginInvoke is preferred? If you ask me, in most cases it's an anti pattern for the exact reasons you mentioned. The question you linked to has many answers, and some actually do suggest using the synchronization context (including mine).

While it's true that any given control could be disposed by the time you're trying to access it from the posted delegate, that's easily solved using Control.IsDisposed (as your delegate is executing on the UI thread so nothing can dispose controls while it's running):

public partial class MyForm : Form
{
    private readonly SynchronizationContext _context;
    public MyForm()
    {
        _context = SynchronizationContext.Current
        //...
    }

    private MethodOnOtherThread()
    {
         //...
         _context.Post(status => 
          {
             // I think it's enough to check the form's IsDisposed
             // But if you want to be extra paranoid you can test someLabel.IsDisposed
             if (!IsDisposed) {someLabel.Text = newText;}
          },null);
    }
}
Fluor answered 16/7, 2016 at 13:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.