У меня есть тип элемента управления, который содержит GridView и некоторые служебные кнопки. Контроль используется везде в моем приложении. Заполняется асинхронно, через делегатов:
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.
}
При длительных запросах пользователь иногда теряет терпение и закрывает окно. Или пользователь случайно закрыл окно, когда один из элементов управления автоматически обновлялся на основе таймера. Когда это произойдет, и запрос DID завершится, вызов Invoke в делегате обратного вызова завершится с ошибкой InvalidOperationException, поскольку элемент управления не имеет дескриптора окна.
Чтобы исправить это, я попытался использовать встроенное свойство IsHandleCreated:
...
else
{
Action asyncMethod = PopulateWithGetterMethod;
asyncMethod.BeginInvoke(
ar => { if(IsHandleCreated)
Invoke((MethodInvoker)InitializeGridView));
}, null);
}
Однако, исключение все еще случается, но не так часто. Мне удалось воспроизвести его, и обнаружил, что вызов Invoke все еще происходит, хотя часы на IsHandleCreated показывали false. Я предполагаю, что поток был прерван между проверкой и вызовом Invoke, как вы видели бы при проверке делегата события на null перед его поднятием.
У меня все еще есть варианты, я думаю, но мне интересно, что лучше:
- Проверьте не только IsHandleCreated, но и Disposing, чтобы убедиться, что элемент управления действительно жив и здоров, а не ТОЛЬКО, который вот-вот будет уничтожен.
- Выполните Thread.Yield () перед проверкой, чтобы ОС могла выполнить любое управление окнами перед проверкой дескриптора.
- Обернуть вызов Invoke в try / catch, который подавляет любые исключения InvalidOperationException или, по крайней мере, те, которые сообщают об отсутствии дескриптора окна. Честно говоря, в этом случае мне все равно, что GridView не может быть обновлен; пользователь закрыл окно, так что, очевидно, им все равно. Пусть поток спокойно умирает, не удаляя все приложение.
Третий вариант выглядит как отбой; должен быть более чистый способ справиться с этим. Но я не уверен, что любой из двух других будет исправлен на 100%.
EDIT : проверка утилизации и IsDisposed тоже не работала; Я получил исключение изнутри блока if с условием «IsHandleCreated &&! Disposing &&! IsDisposed», в котором первый и последний узлы были ложными при просмотре. В настоящее время я перехватываю все исключения сообщением «Invoke или BeginInvoke не могут быть вызваны для элемента управления до тех пор, пока не будет создан дескриптор окна». Это то, чего я надеялся не делать.