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 ca开发者_JS百科ll 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.
Yes, there's a 100% clean way to do this: terminate the thread before you allow the form to close. Anything else is a band-aid over a nasty cut, checking if the form is still alive is an inevitable race condition that you cannot solve. You can only minimize the odds that the form is gonzo when you call Invoke(), you can't eliminate them.
Check this answer for the pattern.
Disposing is your best bet; however, we have this same problem every once in awhile and sometimes the Disposing call returns false but by the time we try to use the control it's disposed, even if it's 3 lines later.
In these cases, we've found it best to catch a specific exception and also verify the exception text contains what we're looking for. If it's an exception and it's a known exception, then swallowing it isn't a terrible idea. The thing is to make sure you're only swallowing the specific exception you're looking for.
精彩评论