Попытка избежать исключений при обратном вызове в поток пользовательского интерфейса - PullRequest
1 голос
/ 21 апреля 2011

У меня есть тип элемента управления, который содержит 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 не могут быть вызваны для элемента управления до тех пор, пока не будет создан дескриптор окна». Это то, чего я надеялся не делать.

Ответы [ 2 ]

4 голосов
/ 21 апреля 2011

Да, есть 100% чистый способ сделать это: прервать поток, прежде чем разрешить закрывать форму.Все остальное - лейкопластырь на неприятном порезе, проверка того, что форма еще жива, - неизбежное условие гонки, которое вы не можете решить.Вы можете только минимизировать шансы, что форма gonzo, когда вы вызываете Invoke (), вы не можете их устранить.

Проверьте этот ответ для шаблона.

1 голос
/ 21 апреля 2011

Утилизация - ваша лучшая ставка; тем не менее, у нас возникает одна и та же проблема время от времени, и иногда вызов Disposing возвращает false, но к тому времени, когда мы пытаемся использовать элемент управления, он удаляется, даже если это на 3 строки позже.

В этих случаях мы нашли, что лучше всего поймать конкретное исключение, а также убедиться, что текст исключения содержит то, что мы ищем. Если это исключение и это известное исключение, то проглатывание не является ужасной идеей. Дело в том, чтобы убедиться, что вы проглатываете только то исключение, которое ищете.

...