Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?
I like the general idea, but I do see one problem. It is important to process EndInvokes, or you can have resource leaks. I know a lot of people don't believe this, but it really is true.
Here's one link talking about it. There are others as well.
But the main response I have is: Yes, I think you've got a nice idea here.
Control.Invoke() vs. Control.BeginInvoke()
Yes, it will be executed on the dispatcher of the UI thread that created it.
As for creating a control on a background thread, I wouldn't advise it. Furthermore, you haven't mentioned what technology you are working with.
Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?
Your scenario, as described, neatly fits BackgroundWorker
- why not just use that? Your requirements for a solution are way too generic, and rather unreasonable - I doubt there is any solution that would satisfy them all.
Is it safe to instantiate a form and call ShowDialog from within BeginInvoke?
This is ambiguous, sure sounds like you instantiate the form first and then call BeginInvoke(). No, that's not okay. It will look like it will work since ShowDialog() pumps a message loop. But you'll have a raft of very nasty glitches. The mild stuff is the window not being modal to the other windows in the app. And not having a Z-order relationship with the other windows in your app which can cause it to easily disappear behind another window. Nastier stuff is that the thread probably isn't an STA thread, things like drag + drop, the clipboard and the shell dialogs will not work. The really nasty stuff is getting the SystemEvents class to start firing events on the wrong thread, that problem lasts past the dialog and crashes or hangs your app at unpredictable times later.
Only ever create a form instance on the UI thread. Which means that you must use the BeginInvoke() method of another instance of a form, one that was created earlier. If you are desperate to find one then Application.OpenForms[0] may give you one.
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
It's possible that you're creating your controls on the wrong thread. Consider the following documentation from MSDN:
This means that InvokeRequired can
return false if Invoke is not required
(the call occurs on the same thread),
or if the control was created on a
different thread but the control's
handle has not yet been created.In the case where the control's handle
has not yet been created, you should
not simply call properties, methods,
or events on the control. This might
cause the control's handle to be
created on the background thread,
isolating the control on a thread
without a message pump and making the
application unstable.You can protect against this case by
also checking the value of
IsHandleCreated when InvokeRequired
returns false on a background thread.
If the control handle has not yet been
created, you must wait until it has
been created before calling Invoke or
BeginInvoke. Typically, this happens
only if a background thread is created
in the constructor of the primary form
for the application (as in
Application.Run(new MainForm()),
before the form has been shown or
Application.Run has been called.
Let's see what this means for you. (This would be easier to reason about if we saw your implementation of SafeInvoke also)
Assuming your implementation is identical to the referenced one with the exception of the check against IsHandleCreated, let's follow the logic:
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Consider the case where we're calling SafeInvoke
from the non-gui thread for a control whose handle has not been created.
uiElement
is not null, so we check uiElement.InvokeRequired
. Per the MSDN docs (bolded) InvokeRequired
will return false
because, even though it was created on a different thread, the handle hasn't been created! This sends us to the else
condition where we check IsDisposed
or immediately proceed to call the submitted action... from the background thread!
At this point, all bets are off re: that control because its handle has been created on a thread that doesn't have a message pump for it, as mentioned in the second paragraph. Perhaps this is the case you're encountering?
Is there any difference between using Invoke for the parent form or for the target control?
These two have the same effect. You can assume the form and the control were created on the same thread.
Reasons that Control.BeginInvoke would not execute a delegate?
According to MSDN InvokeRequired
can return false
even in cases where InvokeRequired
should be true
- namely in the case that you access InvokeRequired
before the Handle
of that control/form (or a parent of it) has been created.
Basically your check is incomplete which leads to the result you see.
You need to check IsHandleCreated
- if that is false
then you are in trouble because an Invoke
/BeginInvoke
would be necessary BUT won't work robustly since Invoke
/BeginInvoke
check which thread created Handle
to do their magic...
Only if IsHandleCreated
is true
you act based on what InvokeRequired
returns - something along the lines of:
if (control.IsHandleCreated)
{
if (control.InvokeRequired)
{
control.BeginInvoke(action);
}
else
{
action.Invoke();
}
}
else
{
// in this case InvokeRequired might lie - you need to make sure that this never happens!
throw new Exception ( "Somehow Handle has not yet been created on the UI thread!" );
}
Thus the following is important to avoid this problem
Always make sure that the Handle
is already created BEFORE the first access on a thread other than the UI thread.
According to MSDN you just need to reference control.Handle
in the UI thread to force it being created - in your code this must happen BEFORE the very first time you access that control/form from any thread that is not the UI thread.
For other possibilities see the answer from @JaredPar .
Best Way to Invoke Any Cross-Threaded Code?
You also could use an extension method and lambdas to make your code much cleaner.
using System.ComponentModel;
public static class ISynchronizeInvokeExtensions
{
public static void InvokeEx<T>(this T @this, Action<T> action) where T : ISynchronizeInvoke
{
if (@this.InvokeRequired)
{
@this.Invoke(action, new object[] { @this });
}
else
{
action(@this);
}
}
}
So now you can use InvokeEx
on any ISynchronizeInvoke and be able to access the properties and fields of implementing class.
this.InvokeEx(f => f.listView1.Items.Clear());
Related Topics
Passing Data to Master Page in ASP.NET MVC
What's the Difference Between Iequatable and Just Overriding Object.Equals()
Why Use Try {} Finally {} with an Empty Try Block
What Is the Point of Lookup<Tkey, Telement>
Programmatically Get a Screenshot of a Page
How Abstraction and Encapsulation Differ
Why Are C# Interface Methods Not Declared Abstract or Virtual
Random Number Between 2 Double Numbers
Convert String to Nullable Type (Int, Double, etc...)
How to Get the Index of an Item in a List in a Single Step
Performance of Static Methods VS Instance Methods
Can a Unit Test Project Load the Target Application's App.Config File
Httpwebrequest Is Extremely Slow!
How to Get the Time Difference Between Two Datetime Objects Using C#