在 ContinueWith() 块中使用 await

Using await inside a ContinueWith() block

我有以下代码:

var result = MessageBoxHelper.MsgBox
    .ShowAsync("Press Yes to proceed", MessageBoxButton.YesNo)
    .ContinueWith((answer) =>
    {
        if (answer.Result == MessageBoxResult.Yes)
        {
            Task<bool> asyncTask = ExecuteAsyncFunc();
            //asyncTask.Unwrap(); ??
            asyncTask.ContinueWith((a) =>
            {
                // More 
            }, TaskContinuationOptions.OnlyOnRanToCompletion);
        }
    }, TaskContinuationOptions.OnlyOnRanToCompletion);
}

像这样在别处调用:

public async Task<bool> ExecuteAsyncFunc()
{
    await CallAnotherAsyncFunction();
}

我认为我需要在我尝试过的地方调用 Unwrap(),因为我在 ContinueWith() 块内调用 await。但是,当我取消注释时出现以下错误:

Error CS1929 'Task' does not contain a definition for 'Unwrap' and the best extension method overload 'TaskExtensions.Unwrap(Task)' requires a receiver of type 'Task'

我是否需要在此上下文中使用 Unwrap,如果需要,我做错了什么?

你说你必须调用 unwrap 因为你在 ContinueWith 中使用了 await;我实际上并没有看到你使用等待。我假设你的意思是你想等待但不确定如何等待,因此需要考虑展开。如果是这种情况,那么您想要的只是让您的嵌套任务可等待。您可以通过让您提供的代表 async

来做到这一点
var result = MessageBoxHelper.MsgBox
    .ShowAsync("Press Yes to proceed", MessageBoxButton.YesNo)
    .ContinueWith(async (answer) =>
    {
        if (answer.Result == MessageBoxResult.Yes)
        {
            await ExecuteAsyncFunc();
        }
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

public async Task<bool> ExecuteAsyncFunc()
{
    await CallAnotherAsyncFunction();
}

这允许您在委托中等待,在 ContinueWith 调用中,而不必处理展开。

如果您打算对 Task 做任何事情,例如 return 它不展开,那么这是正确的方法。

public Task<BoolOrSomeOtherReturnType> Foo()
{
    return MessageBoxHelper.MsgBox
    .ShowAsync /* Continue with etc here */
}

但是如果你打算在Foo方法中对结果进行操作,那么就没有必要使用ContinueWith,只需要等待它。

public async Task<bool> Foo()
{
    var result = await MessageBoxHelper.MsgBox
        .ShowAsync("Press Yes to proceed", MessageBoxButton.YesNo);

    if (result == MessageBoxResult.Yes)
    {
        await ExecuteAsyncFunc();
        return true;
    }

    return false;
}

这会稍微简化您的代码。另一件需要注意的事情是,您的 ExecuteAsyncFunc() 方法不会作用于 return 值。您只需等待,什么都不做。

我注意到您没有 returning true/false,所以我假设您为了清楚起见省略了更多代码。如果不是这种情况,您可以节省一些开销,只需 return 任务,让调用堆栈更上层的人等待它。如果您调用 CallAnotherAsyncFunction returns Task<bool> 那么您也可以 return 以相同的方式调用。只有在必须阻止该方法继续执行时才需要等待,以便您可以对等待方法的结果做出反应。如果不需要,只需 return 任务。

public Task<bool> ExecuteAsyncFunc()
{
    return CallAnotherAsyncFunction();
}

Do I need to use Unwrap in this context, and if so, what am I doing wrong?

只有当您的 return 类型是 Task<Task> 并且您确实想对内部任务做一些事情时,您才需要 Unwrap

例如:

Task<Task> exampleTask = Task.Factory.StartNew(async () => 
{
   await Task.Delay(1000); 
});

如果我真的想异步等待内部 Task,我会调用 Unwrapawait

Task exampleTask = await Task.Factory.StartNew(async () => 
{
   await Task.Delay(1000); 
}).Unwrap();

如果出于任何原因你想要 await 从你的延续中 return 任何任务,或者想要监控内部任务,那么你会调用 Unwrap .这里没有必要这样做。

您所做的只是在 ContinueWith 中调用一个异步方法,但您似乎根本不想 await 结果。

我建议尽可能使用 async-await 以减少 ContinueWith 的冗长。将这两者混合在一起通常会产生不好的结果,因为事情会变得相当复杂。重构代码最终变得更清晰,圈复杂度更低:

var result = await MessageBoxHelper.MsgBox.ShowAsync("Press Yes to proceed",
                                                      MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
    bool answer = await ExecuteFuncAsync();
}

简短的回答是使用 await 而不是 ContinueWith:

var result = MessageBoxHelper.MsgBox.ShowAsync("Press Yes to proceed", MessageBoxButton.YesNo);
if (answer == MessageBoxResult.Yes)
{
  var a = await ExecuteAsyncFunc();
  // More 
}

完整的答案是 ContinueWith has some dangerous default options (including using an ambient TaskScheduler),而 await 会自动为您正确完成所有事情。我会在我的博客上详细介绍。