Error on invoke when the form has closed already
I am trying to display some information on a grid queried from a sql server. The data gathering can take about 10 seconds so I don't want to lock the UI thread.
I currently have code like:
ThreadPool.QueueUserWorkItem(DataUpdateThread, new UpdateParams(year));
private struct UpdateParams
{
internal string year;
internal UpdateParams(string year)
{
this.year = year;
}
}
private void DataUpdateThread(object state)
{
DataTable dTable = new DataTable();
try
{
this.Invoke((MethodInvoker)delegate
{
//stop data editing on the grid
//scrolling marquee for user
marquee.Visible = true;
marquee.Enabled = true;
grdMain.Visible = false;
grdMain.DataSource = null;
});
UpdateParams parameters = (UpdateParams)state;
dTable = GetData(parameters.year);
}
catch开发者_JS百科 (Exception ex)
{
this.Invoke((MethodInvoker)delegate
{
//log error + end user message
});
}
finally
{
this.Invoke((MethodInvoker)delegate
{
grdMain.DataSource = dTable;
grdMainLevel1.RefreshData();
marquee.Visible = false;
marquee.Enabled = false;
grdMain.Visible = true;
});
}
}
This works most of the time apart from if the form it is on is closed before the update completes it will crash with the error:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
I understand the error will be because the form no longer exists so when the finally section tries to invoke the method on the UI thread it can't.
Is there a better way to do this whole thing? I guess I can handle the invoke errors but it looks messy and I think I have probably missed a simpler way.
You can check whether the form has been closed and don't do the invoke if the form has been closed. if (this.IsHandleCreated)
should work. This can however still give problems because the form can be closed between the check and the call to BeginInvoke
. The only 'full-proof' solution is then to enclose the entire call in a try/catch.
Try to use InvokeRequired()
before Invoke()
Invoke
uses a special WinForms SynchronizationContext
behind the scenes that you can access with SynchronizationContext.Current
anywhere in your app.
CORRECTION after some poking in Reflector: actually Invoke goes the direct way of marshalling via PostMessage, it's the BackgroundWorker that makes use of SynchronizationContext behind the scenes. Invoke will throw if it does not have a window handle.
Basically you need to store it in a variable before you start the thread, e.g. while you're still in your UI thread, and use Post
or Send
method of the context in the thread's code. Doing so will marshal the stuff properly without window handles.
精彩评论