understanding InvalidAsynchronousStateException occurrences
When does InvalidAsynchronousStateException get thrown?
I have the following piece of code:
control.InvokeRequired ? control.Invoke(expression) : expression();
In some random cases I get InvalidAsynchronousStateException and my application hangs, after doing some reading it seems to be that this exception will be thrown when the thread where the control
was created finished. Is this correct? If so, this doesn't seem to be the case, unless something is making my application crash and this exception is just a consequence? is this possible?
System.ComponentModel.InvalidAsynchronousStateException: An error occurred invoking the method. The destination thread 开发者_Python百科no longer exists. at System.Windows.Forms.Control.WaitForWaitHandle(WaitHandle waitHandle) at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at Optimus.Desktop.Framework.Spring.Aspects.UIThreadInterceptor.Invoke(IMethodInvocation invocation) in c:\Optimus\Desktop\Framework\Spring\Aspects\UIThreadInterceptor.cs:line 22 at Spring.Aop.Framework.AbstractMethodInvocation.Proceed() at Spring.Aop.Framework.DynamicProxy.AdvisedProxy.Invoke(Object proxy, Object target, Type targetType, MethodInfo targetMethod, MethodInfo proxyMethod, Object[] args, IList interceptors) at InheritanceAopProxy_4fda07e8828744839065a154b30915ee.Dispose(Boolean disposing) at System.ComponentModel.Component.Finalize()
btw, I've checked this answer and didn't clarify my doubt -> InvalidAsynchronousStateException in function that checks if invoke is required for control
Usually this occurs when a background thread is attempting to invoke to a UI thread after the UI thread has already exited. Do you by any chance attempt to run different forms each in their own thread, or do you Show() forms from a non-UI thread, or Invoke() to a form before it is shown?
The background is as follows:
1) Every control (including Forms) has a handle. This is used to tie the control back to the underlying windows GDI objects.
2) The control's handle is usually not created when the control itself is created. The handle is created when the control is Show()n for the first time.
3) When Invoking to a control, the .NET API attempts to locate the control's UI thread using it's handle. If the form has not yet been shown, the CURRENT THREAD (the invoking thread) will be assigned as the UI thread.
4) The UI thread for a control is expected to run a message loop for handling that control (which happens automatically when you do, for instance, Application.Run(someForm);
5) So the common mistake is that you create a form F, Invoke() or BeginInvoke() to it from a temporary or threadpool thread, which creates the form's handle and is therefore assigned as the form's UI thread. Then the background thread exits, or is terminated by the threadpool, or simply fails to run a message loop, since it is not aware that it has been designated a UI thread. Subsequently, any invocations to that form fail with this exception. The exception is thrown simply because the form's assigned 'UI thread' is not running a message loop.
See Ivan's post for a detailed analysis of how this happens: http://www.ikriv.com/en/prog/info/dotnet/MysteriousHang.html
I've been faced to the same issue recently. My form contains several invisible user controls that may be required to appear later in the life cycle of the application. Sometimes, those requests come from background threads.
The problem was that even if I enclose control.Visible = true
inside a control.Invoke
, the control was actually assigned to the background thread (as mentioned in Chris's point #3) instead of the form's main UI thread. A simple workaround for me was to call once the IWin32Window.Handle
property during the creation of the parent form (for instance from the form's Load event) This ensures that the control is created in main UI thread without making it visible.
public partial class MyForm : Form
{
private void MyForm_Load(object sender, EventArgs e)
{
ForceControlCreation(control1);
ForceControlCreation(control2);
}
private void ForceControlCreation(IWin32Window control)
{
// Ensures that the subject control is created in the same thread as the parent
// form's without making it actually visible if not required. This will prevent
// any possible InvalidAsynchronousStateException, if the control is later
// invoked first from a background thread.
var handle = control.Handle;
}
}
As others have correctly shown this happens when a UI component is disposed or is completed (return) while a different thread is still invoking code on the same UI component.
This usually happens when a user closes a window (a form) that has been updated by a different thread and this is more prevalent when the update frequency is high (because the chance of having an incomplete invoke when the user closes the form is high).
This can be gracefully handled by:
- Set a flag to indicate an invoke is in progress
- Intercept the UI dispose or return
- Stop further (new) invokes from taking place
- Wait until existing invokes finish
- Complete the intended dispose or return
Below example shows how to gracefully handle the most common scenario (when a form is closed while it's been updated).
The example is from a simple form that has a listbox that is updated from an outside thread via a public method (AddItem(string)).
Flags
private bool invokeInProgress = false;
private bool stopInvoking = false
Invoking code
public void AddItem(string newItem)
{
if (listView1.InvokeRequired)
{
if (stopInvoking != true) // don't start new invokes if the flag is set
{
invokeInProgress = true; // let the form know if an invoke has started
listView1.Invoke(new Action(() => addItem(newItem))); // invoke
invokeInProgress = false; // the invoke is complete
}
return;
}
listView1.Items.Add(newItem);
listView1.Items[listView1.Items.Count - 1].EnsureVisible();
}
Intercepting and managing form closing event
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (invokeInProgress)
{
e.Cancel = true; // cancel the original event
stopInvoking = true; // advise to stop taking new work
// now wait until current invoke finishes
await Task.Factory.StartNew(() =>
{
while (invokeInProgress);
});
// now close the form
this.Close();
}
}
You can further extend this by exposing a method or a property that lets the users (other threads) know that the form is shutting down so that the callers can gracefully handle the situation.
Example below shows how a new property (ShuttingDown) allows a caller to handle its flow correctly if a user closes the display form.
New flag on form
public bool ShuttingDown { get { return stopInvoking; } }
Caller now can detect the problem
static void Main()
{
Form1 frm = new Form1();
Task.Factory.StartNew(() => frm.ShowDialog(), TaskCreationOptions.LongRunning);
int i = 0;
while (i < 2000)
{
if (frm.ShuttingDown != true) // the clients can also be notified and allowed to handle the UI disruption
{
frm.addItem(Guid.NewGuid().ToString());
}
else
{
MessageBox.Show("Form is closing. Stopping the process.");
break;
}
i++;
}
MessageBox.Show("Program completed! i=" + i.ToString());
}
You can read more and download the sample project from here: http://www.ilearnttoday.com/c-sharp-the-destination-thread-no-longer-exists
精彩评论