Is It Appropriate to Extend Control to Provide Consistently Safe Invoke/Begininvoke Functionality

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



Leave a reply



Submit