So here is what I'm trying to achieve. I launch a task and don't do wait/result on it. To ensure that if launched task goes to faulted state (for e.g. say throw an exception) I crash the process by calling Environment FailFast in Continuation.
How the problem I'm facing is that If I ran below code, Inside ContinueWith, the status of the task (which threw exception) shows up as "RanToCompletion". I expected it to be Faulted State.
private Task KickOfTaskWorkAsync()
{
var createdTask = Task.Run(() => this.RunTestTaskAsync(CancellationToken.None).ConfigureAwait(false), CancellationToken.None);
createdTask.ContinueWith(
task => Console.WriteLine("Task State In Continue with => {0}", task.Status));
return createdTask;
}
private async Task RunTestTaskAsync(CancellationToken cancellationToken)
{
throw new Exception("CrashingRoutine: Crashing by Design");
}
This is really strange :( If I remove the 'ConfigureAwait(false)' inside Task.Run function call, the task does goes to Faulted state inside Continue with. Really at loss to explain what's going on and would appreciate some help from community.
[Update]: My colleague pointed out an obvious error. I am using ConfigureAwait while I make a call to RunTestAsync inside Test.Run even though I don't await it. In this case, ConfigureAwait doesn't return a Task to Task.Run. If I don't call ConfigureAwait, a Task does get returned and things work as expected.
Your error is a specific example of a broader category of mistake: you are not observing the Task
you actually care about.
In your code example, the RunTestTaskAsync()
returns a Task
object. It completes synchronously (because there's no await
), so the Task
object it returns is already faulted when the method returns, due to the exception. Your code then calls ConfigureAwait()
on this faulted Task
object.
But all of this happens inside another Task
, i.e. the one that you start when you call Task.Run()
. This Task
doesn't do anything to observe the exception, so it completes normally.
The reason you observe the exception when you remove the ConfigureAwait()
call has nothing to do with the call itself. If you left the call and passed true
instead, you would still fail to observe the exception. The reason you can observe the exception when you remove the call is that, without the call to ConfigureAwait()
, the return value of the lambda expression is a Task
, and this calls a different overload of Task.Run()
.
This overload is a bit different from the others. From the documentation:
Queues the specified work to run on the thread pool and returns a proxy for the task returned by function.
That is, while it still starts a new Task
, the Task
object it returns represents not that Task
, but the one returned by your lambda expression. And that proxy takes on the same state as the Task
it wraps, so you see it in the Faulted
state.
Based on the code you posted, I would say that you shouldn't be calling Task.Run()
in the first place. The following will work just as well, without the overhead and complication of the proxy:
static void Main(string[] args)
{
Task createdTask = RunTestTaskAsync();
createdTask.ConfigureAwait(false);
createdTask.ContinueWith(
task => Console.WriteLine("Task State In Continue with => {0}", task.Status)).Wait();
}
private static async Task RunTestTaskAsync()
{
throw new Exception("CrashingRoutine: Crashing by Design");
}
(I removed the CancellationToken
values, because they have nothing at all to do with your question and are completely superfluous here.)