取消任务关闭窗口。如何检测任务是否同步返回?

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)

Gab*_*uci 7

例外情况是,Close()OnClosing事件正在运行时您无法调用。我想你明白这一点。

有两种方法可以处理这个问题。

首先,使用Herohtar在评论中提到的答案await Task.Yield()

更具体地说,关键是等待任何不完整的Task.

原因是因为async方法开始同步运行,就像任何其他方法一样。该await关键字仅在给出不完整的情况下才执行任何重要操作Task。如果给定的 aTask已经完成,则该方法将同步继续

那么让我们看一下您的代码。首先我们假设somethingtrue

  1. MainWindow_OnClosing开始同步运行。
  2. ShouldCancelClose开始同步运行。
  3. TryExit()被调用并返回一个不完整的Task.
  4. await关键字看到不完整Task并返回不完整Task。控制权返回到MainWindow_OnClosing
  5. in看到一个不完整的,所以它返回await。由于返回类型为,因此它不返回任何内容。MainWindow_OnClosingTaskvoid
  6. 控制权返回到表单,并且由于它无法等待 的其余部分MainWindow_OnClosing,因此它假定事件处理程序已完成。
  7. 每当TryExit()完成时,其余的ShouldCancelCloseMainWindow_OnClosing就会运行。
  8. 如果Close()现在调用,它就可以工作,因为据表单所知,事件处理程序在步骤 6 处完成

现在我们假设somethingfalse

  1. MainWindow_OnClosing开始同步运行。
  2. ShouldCancelClose开始同步运行。
  3. ShouldCancelClose返回Task值为 的已完成false
  4. awaitin 关键字查看MainWindow_OnClosing已完成并继续同步Task运行该方法。
  5. 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)