I have a type of control that holds a GridView and some utility buttons. The control is used everywhere in my application. It is populated asynchronously, through delegates:
protected virtual void PopulateGridView()
{
if (isPopulating) return;
//a delegate given to the control by its parent form
if (GetterMethod != null)
{
isPopulating = true;
/*unimportant UI fluff here*/
//some controls are fast enough to not have to mess with threading
if(PopulateSynchronously)
{
PopulateWithGetterMethod();
InitializeGridView();
}
else //most aren't
{
Action asyncMethod = PopulateWithGetterMethod;
asyncMethod.BeginInvoke(
ar => Invoke((MethodInvoker)InitializeGridView)), null);
}
}
}
private void PopulateWithGetterMethod()
{
//a list of whetever the control is displaying;
//the control ancestor and this collection are generic.
RetrievedInformation = GetterMethod();
}
protected virtual void InitializeGridView()
{
//use RetrievedInformation to repopulate the GridView;
//implementation not important, except it touches UI elements,
//so it needs to be called from the worker thread using Invoke.
}
On long-running queries, sometimes the user would get impatient and close the window. Or, the user would serendipitously close a window when one of the controls was auto-refreshing based on a Timer. When that happened, and the query DID finish, the Invoke call in the callback delegate would fail with an InvalidOperationException because the control didn't have a window handle.
To fix this, I attempted to use the built-in IsHandleCreated property:
...
else
{
Action asyncMethod = PopulateWithGetterMethod;
asyncMethod.BeginInvoke(
ar => { if(IsHandleCreated)
Invoke((MethodInvoker)InitializeGridView));
}, null);
}
However, the exception still happens, just not as often. I managed to reproduce it, and found that the Invoke call still happened, even though the watch on IsHandleCreated showed false. My guess is that the thread was pre-empted between the check and the Invoke call, like you'd see with checking an event delegate for null before raising it.
I still have options, I think, but I'm wondering what the best is:
- Check not only IsHandleCreated, but Disposing, to make sure the control really is alive and well, and not JUST about to be destroyed.
- Perform a Thread.Yield() before making the check, to allow the OS a chance to do any window management before checking for the handle.
- Wrap the Invoke call in a try/catch that suppresses any InvalidOperationExceptions, or at least ones reporting the lack of a window handle. Honestly, in this case, I don't care that the GridView can't be updated; the user closed the window, so obviously they don't care. Let the thread die quietly, without taking down the whole app.
The third option seems like a cop-out; there has to be a cleaner way to handle it. But, I'm not sure that either of the other two will be a 100% fix.
EDIT: Checking Disposing and IsDisposed didn't work either; I got an exception thrown out from within an if block with the condition "IsHandleCreated && !Disposing && !IsDisposed", in which the first and last nodes were false when watched. Currently I'm trapping all exceptions with the message "Invoke or BeginInvoke cannot be called on a control until the window handle has been created.", which is what I'd hoped not to do.