Using await inside a ContinueWith() block

2019-06-23 22:24发布

I have the following code:

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);
}

Invoked elsewhere like this:

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

I believe that I need to call Unwrap(), where I have tried to, because I am calling await inside a ContinueWith() block. However, I get the following error when I uncomment it:

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'

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

3条回答
Root(大扎)
2楼-- · 2019-06-23 22:52

The short answer is to use await instead of ContinueWith:

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

The long answer is that ContinueWith has some dangerous default options (including using an ambient TaskScheduler), and await automatically does everything correctly for you. I go into more detail on my blog.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-06-23 22:54

You say that you have to call unwrap due to your using await inside the ContinueWith; I don't actually see you using await though. I assume what you meant is that you want to await but are not sure how, and thus thought unwrapping is needed. If that is the case, then what you want is to just make your nested Task awaitable. You can do that by making the delegate you provide 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();
}

This allows you to await within the delegate, within the ContinueWith call and not have to deal with unwrapping.

If you are going to do anything with the Task, such as return it without unwrapping, then this is the right way to go.

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

But if you are going to act on the results within the Foo method, then there is no need to use ContinueWith, just await it.

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;
}

That simplifies your code quiet a bit. The other thing to note is that your ExecuteAsyncFunc() method does not act on the return value. You simply await and do nothing else.

I noticed that you're not returning true/false, so I assume there is more code there that you just omitted for clarity sake. If that isn't the case, you can save yourself some overhead and just return the Task, allowing someone further up the callstack to await it instead. If your call to CallAnotherAsyncFunction returns Task<bool> then you can just return that call in the same way as well. You only need to await if you have to prevent the method from going any further so you can react to the result of the awaited method. If you don't have to, just return the Task.

public Task<bool> ExecuteAsyncFunc()
{
    return CallAnotherAsyncFunction();
}
查看更多
一纸荒年 Trace。
4楼-- · 2019-06-23 23:01

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

You need to Unwrap only when your return type is a Task<Task>, and you actually want to do something with the inner task.

For example:

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

If I wanted to actually asynchronously wait on the inner Task, i'd call Unwrap and await on that:

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

If for any reason you'd want to await on any task returned from your continuation, or want to monitor the inner task, then you'd call Unwrap. There is no need to do that here.

What you're doing is simply invoking an async method inside your ContinueWith, but it doesn't seem like you want to await the result at all.

I'd recommend using async-await wherever possible to reduce the verbosity of ContinueWith. Mixing those two together usually yields bad results as thing get rather complicated. Refactoring that code ends up much cleaner with much less cyclomatic complexity:

var result = await MessageBoxHelper.MsgBox.ShowAsync("Press Yes to proceed",
                                                      MessageBoxButton.YesNo);
if (result == MessageBoxResult.Yes)
{
    bool answer = await ExecuteFuncAsync();
}
查看更多
登录 后发表回答