Reputation: 761
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
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
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 Task
s 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