Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using await inside a ContinueWith() block

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?

like image 994
Paul Michaels Avatar asked Jun 29 '15 17:06

Paul Michaels


People also ask

What does calling task ContinueWith () do?

ContinueWith(Action<Task,Object>, Object, TaskScheduler)Creates a continuation that receives caller-supplied state information and executes asynchronously when the target Task completes.

Does C# await block?

The await keyword, by contrast, is non-blocking, which means the current thread is free to do other things during the wait.

How async await works internally?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.

What is ContinueWith C#?

The ContinueWith function is a method available on the task that allows executing code after the task has finished execution. In simple words it allows continuation. Things to note here is that ContinueWith also returns one Task. That means you can attach ContinueWith one task returned by this method.


3 Answers

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.

like image 173
Stephen Cleary Avatar answered Oct 16 '22 08:10

Stephen Cleary


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();
}
like image 3
Yuval Itzchakov Avatar answered Oct 16 '22 08:10

Yuval Itzchakov


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();
}
like image 2
Johnathon Sullinger Avatar answered Oct 16 '22 08:10

Johnathon Sullinger