Smo*_*ian 2 c# wpf async-await
我遵循一种相当常见的模式,使用异步对话框方法确认/取消主窗口关闭。但是,在我调用来呈现对话框的异步任务中,在某些情况下我会立即返回布尔值,而不是等待对话框任务方法的返回。在这些情况下会抛出异常:
System.InvalidOperationException:“在窗口关闭时无法将可见性设置为可见或调用 Show、ShowDialog、Close 或 WindowInteropHelper.EnsureHandle。”
看来这是因为异步任务同步返回并在窗口上调用 Close(),而不是调用其余代码作为延续。除了在 try/catch 中包装 Close() 或在返回 bool 之前在函数中添加 Task.Delay() 之外,还有其他方法可以检测是否应该在窗口上调用 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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
异步函数如下所示:
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;
}
Run Code Online (Sandbox Code Playgroud)
例外情况是,Close()当OnClosing事件正在运行时您无法调用。我想你明白这一点。
有两种方法可以处理这个问题。
首先,使用Herohtar在评论中提到的答案await Task.Yield()。
更具体地说,关键是等待任何不完整的Task.
原因是因为async方法开始同步运行,就像任何其他方法一样。该await关键字仅在给出不完整的情况下才执行任何重要操作Task。如果给定的 aTask已经完成,则该方法将同步继续。
那么让我们看一下您的代码。首先我们假设something是true:
MainWindow_OnClosing开始同步运行。ShouldCancelClose开始同步运行。TryExit()被调用并返回一个不完整的Task.await关键字看到不完整Task并返回不完整Task。控制权返回到MainWindow_OnClosing。await。由于返回类型为,因此它不返回任何内容。MainWindow_OnClosingTaskvoidMainWindow_OnClosing,因此它假定事件处理程序已完成。TryExit()完成时,其余的ShouldCancelClose和MainWindow_OnClosing就会运行。Close()现在调用,它就可以工作,因为据表单所知,事件处理程序在步骤 6 处完成。现在我们假设something是false:
MainWindow_OnClosing开始同步运行。ShouldCancelClose开始同步运行。ShouldCancelClose返回Task值为 的已完成false。awaitin 关键字查看MainWindow_OnClosing已完成并继续同步Task运行该方法。Close()调用时,它会抛出异常,因为事件处理程序尚未完成运行。因此, usingawait 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();
}
}
}
Run Code Online (Sandbox Code Playgroud)