Исключение говорит о том, что вы не можете вызвать Close()
, пока событие OnClosing
находится в процессе выполнения. Я думаю, вы понимаете это.
Есть два способа справиться с этим.
Первый , ответ, упомянутый Herohtar в комментариях await Task.Yield()
.
В частности, ключ ожидает любой неполный Task
.
Причина в том, что async
методы запускаются синхронно , как и любой другой метод. Ключевое слово await
имеет значение только в том случае, если ему дано неполное Task
. Если ему дается Task
, что уже выполнено, метод продолжается синхронно .
Итак, давайте пройдемся по вашему коду. Сначала давайте предположим, что something
- это true
:
MainWindow_OnClosing
запускается синхронно. ShouldCancelClose
запускается синхронно. TryExit()
вызывается и возвращает неполное Task
. - Ключевое слово
await
видит неполное Task
и возвращает неполное Task
. Элемент управления возвращается к MainWindow_OnClosing
. - .
await
в MainWindow_OnClosing
видит неполное Task
, поэтому он возвращается. Поскольку тип возвращаемого значения void
, он ничего не возвращает. - Элемент управления возвращается в форму, и, поскольку он не может ожидать оставшуюся часть
MainWindow_OnClosing
, он предполагает, что обработчик события завершен. - Всякий раз, когда
TryExit()
завершается, запускаются остальные ShouldCancelClose
и MainWindow_OnClosing
. - Если сейчас вызывается
Close()
, это работает, потому что , насколько известно форме, событиеобработчик закончил на шаге 6 .
Теперь давайте предположим, что something
равен false
:
MainWindow_OnClosing
запускается синхронно. ShouldCancelClose
начинает работать синхронно. ShouldCancelClose
возвращает заполненное Task
со значением false
. - Ключевое слово
await
в MainWindow_OnClosing
видитзавершено Task
и продолжает выполнение метода синхронно . - Когда вызывается
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();
}
}
}