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?
ContinueWith(Action<Task,Object>, Object, TaskScheduler)Creates a continuation that receives caller-supplied state information and executes asynchronously when the target Task completes.
The await keyword, by contrast, is non-blocking, which means the current thread is free to do other things during the wait.
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.
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.
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.
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();
}
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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With