I have this code:
//note that this Action is a very time consuming process so I need to wrap it in Task.Run
private static async Task RunBeforeCompletion(Action action)
{
var task = Task.Run(() =>
{
Console.WriteLine("start");
action(); //some exception could have happened here, or I could have used the Task incorrectly that results in exception
//how to catch this exception?
});
await task.ContinueWith(t =>
{
Console.WriteLine(t.Exception.Message);
ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
},
TaskContinuationOptions.OnlyOnFaulted);
}
private static void Sleep()
{
Console.WriteLine("sleep");
Thread.Sleep(1000);
}
private static void RunAll()
{
var tsk = RunBeforeCompletion(Sleep)
.ContinueWith(t1 =>
{
Console.WriteLine("run");
});
Task.WaitAll(tsk);
}
static void Main(string[] args)
{
RunAll();
}
Note that the Action
is a very time consuming process so I need to wrap it in Task.Run
Which works fine. The code can run well without exception if there is no exception thrown from the Action
However, if I move the method body of RunBeforeCompletion
to another method, then a TaskCanceledException
will be thrown. ie: the following code will throw a TaskCanceledException
.
private static async Task WrapTask(Action action)
{
var task = Task.Run(() =>
{
Console.WriteLine("start");
action(); //some exception could have happened here, or I could have used the Task incorrectly that results in exception
//how to catch this exception?
});
await task.ContinueWith(t =>
{
Console.WriteLine(t.Exception.Message);
ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
},
TaskContinuationOptions.OnlyOnFaulted);
}
private static async Task RunBeforeCompletion(Action action)
{
await WrapTask(action);
}
private static void Sleep()
{
Console.WriteLine("sleep");
Thread.Sleep(1000);
}
private static void RunAll()
{
var tsk = RunBeforeCompletion(Sleep)
.ContinueWith(t1 =>
{
Console.WriteLine("run");
});
Task.WaitAll(tsk);
}
static void Main(string[] args)
{
RunAll();
}
From what I understand, this is because TaskContinuationOptions.OnlyOnFaulted
only works in a single task and not multi-task continuation.
Do note that the crash will only happen when I run the second case code in VS 2015 with debugger attached and with Exception Settings-> Break
at all Exception.
If I run without a debugger, or if I don't require the VS to break when TaskCanceledException is thrown, then no problem. Whatever it is, TaskCanceledException should never be thrown.
First question:
But aren't the first and second method the same? I just move the original Task
in a separate method.
I believe that I should always be using the TaskContinuationOptions.OnlyOnFaulted
option on a single task only as per guideline, that's why I put it immediately after a Task.Run
, so that I know that I don't unconsciously chain it with other ContinueWith
statement right after a Task.Run
.
And what does this mean? Why the TaskCanceledException
is thrown with debugger attached but not thrown if no debugger? In that case, how can I be sure all the tasks are finishing successfully or not?
What purpose I'm trying to accomplish?
During action
, some exceptions can be thrown, or I might use the Task
channing incorrectly, so I want to log the exception first ( using Console.WriteLine
as a stub in for this toy example), before I rethrow the exception up for further handling. This is because any exceptions thrown inside Task.Run
( or anything to do with Task
) will be swallowed up and later will result in a very mysterious crash. So I want to log the exception.
So my second question is, given that if the user of WrapTask
can chain the method up with other ContinueWith
construct for as long as he wants, how to write the exception handling code ( for the purpose of logging the exception error) elegantly?