user3292642
user3292642

Reputation: 761

Trying to understand Task.ContinueWith()

I'm trying to understand some (in my eyes) weird behaviour. I have a call to some async method and want to retrieve its result. (DeleteIndexAsync return a Task<bool>)

  var deleteTask = Task.Run(() => DeleteIndexAsync(localItem))
    .ContinueWith(t =>
  {
   //handle and log exceptions       
  }, TaskContinuationOptions.OnlyOnFaulted);

 if (!deleteTask.Result)

In this scenario Result is false and the Status is WaitingForActivation.

Whereas this code does what I want

  var deleteTask = Task.Run(() => DeleteIndexAsync(localItem));
  deleteTask.ContinueWith(t =>
   {
     //handle and log exceptions
     return false;
   }, TaskContinuationOptions.OnlyOnFaulted);

  if (!deleteTask.Result)

Can someone explain why? And is it possible to use async / await instead of Task here?

Edit:

  var deleteTask = Task.Run(() => ThrowEx());
  bool errorOccurred = false;
  deleteTask.ContinueWith(t =>
   {         
     errorOccurred = true;
   }, TaskContinuationOptions.OnlyOnFaulted);

  if (errorOccurred)
  {
    return true;
  }

Upvotes: 0

Views: 669

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 456407

I have a call to some async method and want to retrieve its result.

The best way to do this is with await, not Result.

Can someone explain why?

In the first example, deleteTask is the task returned from Task.Run. In the second example, deleteTask is the task returned from ContinueWith. This task never actually executes unless DeleteIndexAsync throws an exception.

And is it possible to use async / await instead of Task here?

Yes, that's the best approach. When working with asynchronous code, you should always use await instead of ContinueWith. In this case, the TaskContinuationOptions.OnlyOnFaulted means you should put the continuation code in a catch block after the await:

bool deleteResult;
try
{
  deleteResult = await Task.Run(() => DeleteIndexAsync(localItem));
}
catch (Exception ex)
{
  //handle and log exceptions
}
if (!deleteResult)
  ...

Or, since it looks like DeleteIndexAsync is asynchronous, removing Task.Run would be more appropriate:

bool deleteResult;
try
{
  deleteResult = await DeleteIndexAsync(localItem);
}
catch (Exception ex)
{
  //handle and log exceptions
}
if (!deleteResult)
  ...

Upvotes: 2

slawekwin
slawekwin

Reputation: 6310

If you chain the calls, like in the first example, the value you are assigning to the deleteTask variable is actually the second Task. This is the one that is supposed to run only on failure of the first task (the one calling DeleteIndexAsync).

This is because both Task.Run and Task.ContinueWith return Tasks that they create. It explains why in the first example you get Status == WaitingForActivation. In the first snippet, accessing deleteTask.Result would cause an exception to be thrown. In case DeleteIndexAsync threw, it would be an AggregateException containing original exception (unless you accessed t.Exception), otherwise it would be stating that the "Operation was cancelled" - this is because you try to get the result of the task that was scheduled conditionally and the condition was not met.

If you made method containing the snipped async you could do it like this (not tested):

bool success = false;
try
{
    success = await DeleteIndexAsync(localItem);
}
catch (Exception) {}

if (!success)
{
    //TODO: handler
}

Regarding question edit:

Using captured variable should help, but your current solution introduces race condition. In this case it would be better to do like this:

var deleteTask = Task.Run(() => ThrowEx());    

try
{
    deleteTask.Wait();
} 
catch (Exception ex)
{
    return true;
}

but at this point you can drop the asynchronous call entirely, because you wait for the result immediately - unless this example simplifies work that can be done between Run and Wait)

Upvotes: 2

Related Questions