Отмена закрытия окна с задачей. Как я могу обнаружить, если задание возвращено синхронно? - PullRequest
2 голосов
/ 05 ноября 2019

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

System.InvalidOperationException: 'Невозможно установить Visibility в Visible или вызвать Show, ShowDialog, Close или WindowInteropHelper.EnsureHandle, когда окно закрывается.'

кажется , что это потому, что асинхронная задача возвращается синхронно и вызывает Close () в окне вместо вызова остальной части кода в качестве продолжения. Помимо простого обтекания Close () в try / catch или добавления Task.Delay () в мою функцию перед возвратом bool, есть ли способ определить, должен ли я вызывать Close () для моего окна? (т. е. если задача вернулась синхронно)

Или ... я концептуально что-то упустил в шаблоне асинхронности / ожидания?

Вот мой код:

private bool _closeConfirmed;

private async void MainWindow_OnClosing(object sender, CancelEventArgs e)
{
    //check if flag set
    if(!_closeConfirmed)
    {
        //use flag and always cancel first closing event (in order to allow making OnClosing work as as an async function)
        e.Cancel = true;

        var cancelClose = await mainViewModel.ShouldCancelClose();

        if(!cancelClose)
        {
            _closeConfirmed = true;
            this.Close();
        }
    }
}

Вот как выглядит асинхронная функция:

public async Task<bool> ShouldCancelClose()
{
    if(something)
    {
        var canExit = await (CurrentMainViewModel as AnalysisViewModel).TryExit();

        if (!canExit) //if user cancels exit
            return true;

        //no exception
        return false;
    }

    //this causes exception
    return false;
}

1 Ответ

3 голосов
/ 05 ноября 2019

Исключение говорит о том, что вы не можете вызвать Close(), пока событие OnClosing находится в процессе выполнения. Я думаю, вы понимаете это.

Есть два способа справиться с этим.

Первый , ответ, упомянутый Herohtar в комментариях await Task.Yield().

В частности, ключ ожидает любой неполный Task.

Причина в том, что async методы запускаются синхронно , как и любой другой метод. Ключевое слово await имеет значение только в том случае, если ему дано неполное Task. Если ему дается Task, что уже выполнено, метод продолжается синхронно .

Итак, давайте пройдемся по вашему коду. Сначала давайте предположим, что something - это true:

  1. MainWindow_OnClosing запускается синхронно.
  2. ShouldCancelClose запускается синхронно.
  3. TryExit() вызывается и возвращает неполное Task.
  4. Ключевое слово await видит неполное Task и возвращает неполное Task. Элемент управления возвращается к MainWindow_OnClosing.
  5. . await в MainWindow_OnClosing видит неполное Task, поэтому он возвращается. Поскольку тип возвращаемого значения void, он ничего не возвращает.
  6. Элемент управления возвращается в форму, и, поскольку он не может ожидать оставшуюся часть MainWindow_OnClosing, он предполагает, что обработчик события завершен.
  7. Всякий раз, когда TryExit() завершается, запускаются остальные ShouldCancelClose и MainWindow_OnClosing.
  8. Если сейчас вызывается Close(), это работает, потому что , насколько известно форме, событиеобработчик закончил на шаге 6 .

Теперь давайте предположим, что something равен false:

  1. MainWindow_OnClosing запускается синхронно.
  2. ShouldCancelClose начинает работать синхронно.
  3. ShouldCancelClose возвращает заполненное Task со значением false.
  4. Ключевое слово await в MainWindow_OnClosing видитзавершено Task и продолжает выполнение метода синхронно .
  5. Когда вызывается Close(), выдается исключение , поскольку обработчик событий не завершил работу .

Таким образом, использование await Task.Yield() - это просто способ ожидания чего-то неполного , чтобы управление возвращалосьформа, которая думает, что обработчик событий завершен.

Секунда , если вы знаете, что асинхронный код не выполнялся, вы можете положиться на e.Cancel, чтобы отменить закрытие или нет. Вы можете проверить, не ожидая Task, пока не узнаете, завершено оно или нет. Это может выглядеть примерно так:

private bool _closeConfirmed;

private async void MainWindow_OnClosing(object sender, CancelEventArgs e)
{
    //check if flag set
    if(!_closeConfirmed)
    {

        var cancelCloseTask = mainViewModel.ShouldCancelClose();

        //Check if we were given a completed Task, in which case nothing
        //asynchronous happened.
        if (cancelCloseTask.IsCompleted)
        {
            if (await cancelCloseTask)
            {
                e.Cancel = true;
            }
            else
            {
                _closeConfirmed = true;
            }
            return;
        }

        //use flag and always cancel first closing event (in order to allow making OnClosing work as as an async function)
        e.Cancel = true;

        if(!await cancelCloseTask)
        {
            _closeConfirmed = true;
            this.Close();
        }
    }
}
...