Consider the following piece of code:
CancellationTokenSource cts0 = new CancellationTokenSource(), cts1 = new CancellationTokenSource();
try
{
var task = Task.Run(() => { throw new OperationCanceledException("123", cts0.Token); }, cts1.Token);
task.Wait();
}
catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
Due to MSDN task should be in Faulted state because it's token does not match exception's token (and also IsCancellationRequested is false):
If the token's IsCancellationRequested property returns false or if the exception's token does not match the Task's token, the OperationCanceledException is treated like a normal exception, causing the Task to transition to the Faulted state.
When I launch this code in console app using .NET 4.5.2 I get task in Canceled state (aggregate exception contains unknown TaskCanceledExeption, not the original). And all information of original exception is lost (message, inner exception, custom data).
I also noticed that behavior of Task.Wait differs from await task in case of OperationCanceledException.
try { Task.Run(() => { throw new InvalidOperationException("123"); }).Wait(); } // 1
catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
try { await Task.Run(() => { throw new InvalidOperationException("123"); }); } // 2
catch (InvalidOperationException ex) { Console.WriteLine(ex); }
try { Task.Run(() => { throw new OperationCanceledException("123"); }).Wait(); } // 3
catch (AggregateException ae) { Console.WriteLine(ae.InnerException); }
try { await Task.Run(() => { throw new OperationCanceledException("123"); }); } // 4
catch (OperationCanceledException ex) { Console.WriteLine(ex); }
Cases 1 and 2 produce almost identical result (differ only in StackTrace), but when I change exception to OperationCanceledException, then I get very different results: an unknown TaskCanceledException in case 3 without original data, and expected OpeartionCanceledException in case 4 with all original data (message, etc.).
So the question is: Does MSDN contain incorrect information? Or is it a bug in .NET? Or maybe it's just I don't understand something?
It is a bug. Task.Run under the hood calls Task<Task>.Factory.StartNew. This internal Task is getting the right Status of Faulted. The wrapping task is not.
You can work around this bug by calling
Task.Factory.StartNew(() => { throw new OperationCanceledException("123", cts0.Token); }, cts1.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
Though, you'll lose the other feature of Task.Run which is unwrapping. See:
Task.Run vs Task.Factory.StartNew
More Details:
Here's the code of Task.Run where you see that it is creating a wrapping UnwrapPromise (which derives from Task<TResult>:
public static Task Run(Func<Task> function, CancellationToken cancellationToken)
{
// Check arguments
if (function == null) throw new ArgumentNullException("function");
Contract.EndContractBlock();
cancellationToken.ThrowIfSourceDisposed();
// Short-circuit if we are given a pre-canceled token
if (cancellationToken.IsCancellationRequested)
return Task.FromCancellation(cancellationToken);
// Kick off initial Task, which will call the user-supplied function and yield a Task.
Task<Task> task1 = Task<Task>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
// Create a promise-style Task to be used as a proxy for the operation
// Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation.
UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1, lookForOce: true);
return promise;
}
The Task constructor which it calls does not take a cancellation token (and thus it does not know about the inner Task's cancellation token). Notice it creates a default CancellationToken instead. Here's the ctor it calls:
internal Task(object state, TaskCreationOptions creationOptions, bool promiseStyle)
{
Contract.Assert(promiseStyle, "Promise CTOR: promiseStyle was false");
// Check the creationOptions. We only allow the AttachedToParent option to be specified for promise tasks.
if ((creationOptions & ~TaskCreationOptions.AttachedToParent) != 0)
{
throw new ArgumentOutOfRangeException("creationOptions");
}
// m_parent is readonly, and so must be set in the constructor.
// Only set a parent if AttachedToParent is specified.
if ((creationOptions & TaskCreationOptions.AttachedToParent) != 0)
m_parent = Task.InternalCurrent;
TaskConstructorCore(null, state, default(CancellationToken), creationOptions, InternalTaskOptions.PromiseTask, null);
}
The outer task (the UnwrapPromise adds a continuation). The continuation examines the inner task. In the case of the inner task being faulted, it consideres finding a a OperationCanceledException as indicating cancellation (regardless of a matching token). Below is the UnwrapPromise<TResult>.TrySetFromTask (below is also the call stack showing where it gets called). Notice the Faulted state:
private bool TrySetFromTask(Task task, bool lookForOce)
{
Contract.Requires(task != null && task.IsCompleted, "TrySetFromTask: Expected task to have completed.");
bool result = false;
switch (task.Status)
{
case TaskStatus.Canceled:
result = TrySetCanceled(task.CancellationToken, task.GetCancellationExceptionDispatchInfo());
break;
case TaskStatus.Faulted:
var edis = task.GetExceptionDispatchInfos();
ExceptionDispatchInfo oceEdi;
OperationCanceledException oce;
if (lookForOce && edis.Count > 0 &&
(oceEdi = edis[0]) != null &&
(oce = oceEdi.SourceException as OperationCanceledException) != null)
{
result = TrySetCanceled(oce.CancellationToken, oceEdi);
}
else
{
result = TrySetException(edis);
}
break;
case TaskStatus.RanToCompletion:
var taskTResult = task as Task<TResult>;
result = TrySetResult(taskTResult != null ? taskTResult.Result : default(TResult));
break;
}
return result;
}
Call stack:
mscorlib.dll!System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>.TrySetCanceled(System.Threading.CancellationToken tokenToRecord, object cancellationException) Line 645 C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.TrySetFromTask(System.Threading.Tasks.Task task, bool lookForOce) Line 6988 + 0x9f bytes C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.ProcessCompletedOuterTask(System.Threading.Tasks.Task task) Line 6956 + 0xe bytes C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.InvokeCore(System.Threading.Tasks.Task completingTask) Line 6910 + 0x7 bytes C#
mscorlib.dll!System.Threading.Tasks.UnwrapPromise<System.Threading.Tasks.VoidTaskResult>.Invoke(System.Threading.Tasks.Task completingTask) Line 6891 + 0x9 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.FinishContinuations() Line 3571 C#
mscorlib.dll!System.Threading.Tasks.Task.FinishStageThree() Line 2323 + 0x7 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.FinishStageTwo() Line 2294 + 0x7 bytes C#
mscorlib.dll!System.Threading.Tasks.Task.Finish(bool bUserDelegateExecuted) Line 2233 C#
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) Line 2785 + 0xc bytes C#
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Line 2728 C#
mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() Line 2664 + 0x7 bytes C#
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Line 829 C#
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() Line 1170 + 0x5 bytes C#
It notices the OperationCanceledException and calls TrySetCanceled to put the task into the cancelled state.
An aside:
Another thing to note is that when you start using async methods, there isn't really a way to register a cancellation token with an async method. Thus, any OperationCancelledException that gets encountered in an async methods is considered a cancellation.
See
Associate a CancellationToken with an async method's Task
Matt Smith - Thank you, your explanation was very helpful.
After reading it and testing for a while I noticed that original question does not fully correct. It's not a problem of Task.Wait. I can get this wrong behavior with Task.ContinueWith, checking first task's Status - it is Canceled. So I believe the final answer is:
If you create a task using Task.Run overloads that take Func<Task> or Func<Task<TResult>> as a first argument, and your delegate throws OperationCanceledException, and if you use Task.Wait or Task.ContinueWith on returned task, then you will lose original exception with all it's data because of bug in .NET (as Matt Smith explained) and get task in incorrect Canceled state instead of Faulted, regardless of matching documented logic.
All of these conditions matter. If you use await on created task - it works fine. If you use Task.Run overloads that take Action or Func<TResult> as a first argument - it works fine in all cases (Wait, ContinueWith, await).
I also noticed strange behavior of overloaded method selection logic. When I write
Task.Run(() => { throw new OperationCanceledException("123", cts0.Token); }, cts1.Token);
I expect to use Task.Run(Action, CancellationToken) overload, which is not broken. But somehow it appears that broken Task.Run(Func<Task>, CancellationToken) is used. So I'm forced to do something like this
Task.Run((Action)(() => { throw new OperationCanceledException("123", cts0.Token); }), cts1.Token);
or use TaskFactory.StartNew.
This behavior is very interesting and strange, at the same time.
The purpose of the AggregateException, as its name says, is to group together multiple exceptions/errors that happen during the execution of an application. So, in your 3rd case, you have an OperationCanceledException as inner exception and the stack trace of the AggregateException should report everything about that, including the data (like 123), as it is shown below in the 4th case:
About your questions:
Does MSDN contain incorrect information?
It should report always the correct and precise information about the behavior of classes, methods and so on.
Or is it a bug in .NET?
Most probably, yes, it is a bug. It's not understandable why this happens. Here you'll find a related question about this issue. Please report this problem to Microsoft.
Related
This question already has an answer here:
Faulted vs Canceled task status after CancellationToken.ThrowIfCancellationRequested
(1 answer)
Closed last month.
According to this and this, passing a cancellation token to a task constructor, or Task.Run, will cause the task to be associated with said token, causing the task to transition to Canceled instead of Faulted if a cancellation exception occurs.
I've been fiddling with these examples for a while, and I can't see any benefits other than preventing a cancelled task to start.
Changing the code on this MSDN example from
tc = Task.Run(() => DoSomeWork(i, token), token);
to
tc = Task.Run(() => DoSomeWork(i, token));
produced the exact same output:
This code also results in two cancelled state tasks with the same exceptions thrown:
var token = cts.Token;
var t1 = Task.Run(() =>
{
while (true)
{
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
};
});
var t2 = Task.Run(() =>
{
while (true)
{
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
};
}, token);
Console.ReadKey();
try
{
cts.Cancel();
Task.WaitAll(t1, t2);
}
catch(Exception e)
{
if (e is AggregateException)
{
foreach (var ex in (e as AggregateException).InnerExceptions)
{
Console.WriteLine(e.Message);
}
}
else
Console.WriteLine(e.Message);
}
Console.WriteLine($"without token: { t1.Status }");
Console.WriteLine($"with token: { t2.Status }");
Console.WriteLine("Done.");
Apparently, throwing OperationCanceledException from within the task is enough to make it transition to Canceled instead of Faulted. So my question is: is there a reason for passing the token to the task other than preventing a cancelled task to run?
Is there a reason for passing the token to the task other than preventing a cancelled task to run?
In this particular case, No. Past the point in time that the task has started running, the token has no effect to the outcome.
The Task.Run method has many overloads. This case is peculiar because of the infinite while loop.
var t1 = Task.Run(() =>
{
while (true)
{
Thread.Sleep(1000);
token.ThrowIfCancellationRequested();
};
});
The compiler has to choose between these two overloads:
public static Task Run(Action action);
public static Task Run(Func<Task> function);
...and for a reason explained in this question it chooses the later. Here is the implementation of this overload:
public static Task Run(Func<Task?> function, CancellationToken cancellationToken)
{
if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
// Short-circuit if we are given a pre-canceled token
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
// Kick off initial Task, which will call the user-supplied function and yield a Task.
Task<Task?> task1 = Task<Task?>.Factory.StartNew(function, cancellationToken,
TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
// Create a promise-style Task to be used as a proxy for the operation
// Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown
// as faults from task1, to support in-delegate cancellation.
UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1,
lookForOce: true);
return promise;
}
The important detail is the lookForOce: true. Let's look inside the UnwrapPromise class:
// "Should we check for OperationCanceledExceptions on the outer task and interpret them
// as proxy cancellation?"
// Unwrap() sets this to false, Run() sets it to true.
private readonly bool _lookForOce;
..and at another point below:
case TaskStatus.Faulted:
List<ExceptionDispatchInfo> edis = task.GetExceptionDispatchInfos();
ExceptionDispatchInfo oceEdi;
if (lookForOce && edis.Count > 0 &&
(oceEdi = edis[0]) != null &&
oceEdi.SourceException is OperationCanceledException oce)
{
result = TrySetCanceled(oce.CancellationToken, oceEdi);
}
else
{
result = TrySetException(edis);
}
break;
So although the internally created Task<Task?> task1 ends up in a Faulted state, its unwrapped version ends up as Canceled, because the type of the exception is
OperationCanceledException (abbreviated as oce in the code).
That's a quite convoluted journey in the history of TPL, with methods introduced at different times and frameworks, in order to serve different purposes. The end result is a little bit of inconsistency, or nuanced behavior if you prefer to say it so. A relevant article that you might find interesting is this: Task.Run vs Task.Factory.StartNew by Stephen Toub.
I have a C# application which executes a method that returns a Task. In this case, I'm trying to show a message box containing the exception details if the called method throws an exception.
If I call it like this, I can see the exception fine in task.Exception:
MyClass.MyAsyncMethod(cancellationToken)
.LogExceptions()
.ContinueWith(task => {
MessageBox.Show(task.Exception);
}, cancellationToken, TaskContinuationOptions.NotOnRanToCompletion, TaskScheduler.Default);
However, if I add an OnlyOnRanToCompletion continuation, task.Exception in the NotOnRanToCompletion continuation becomes null:
MyClass.MyAsyncMethod(cancellationToken)
.LogExceptions()
.ContinueWith(task => {
Log.Info("Executed");
}, cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default)
.ContinueWith(task => {
MessageBox.Show(task.Exception);
}, cancellationToken, TaskContinuationOptions.NotOnRanToCompletion, TaskScheduler.Default);
The code in the OnlyOnranToCompletion continuation is not executed, but the code in the NotOnRanToCompletion continuation is executed and task.Exception is null. Why does this happen?
Note: I cannot use C# >= 5.0 features like async or await. I'm also able to get around this by having it all under a single None continuation method that checks the exception parameter of the task and determines if an exception occured. However, I'm interested in WHY the above behaviour takes place.
This is the called method:
public Task<bool> MyAsyncMethod(CancellationToken cancellationToken)
{
return Task<bool>.Factory.StartNew(() =>
{
...
try
{
var response = request.GetResponse();
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("Invalid response status code: " + response.StatusCode);
}
catch (Exception ex)
{
Logger.Error("Request failed", ex));
throw;
}
}, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
}
There is no exception because the Task that it is a continuation of didn't throw an exception. The Task that it is a continuation of (the previous call to ContinueWith) will have completed successfully.
If you want to handle both the error and non-error case, you're probably better off just having one continuation and checking if it was successful in that continuation, rather than using the continuation options. Alternatively you could store the Task from the actual work that you're doing and add both continuations as continuations to that one Task, rather than having one continuation being a continuation of the other.
In our application we work a lot with async / await and Tasks. Therefore it does use Task.Run a lot, sometimes with cancellation support using the built in CancellationToken.
public Task DoSomethingAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}, cancellationToken);
}
If i do now cancel the execution using the CancellationToken the execution does stop at the beginning of the next loop, or if the Task did not start at all it throws an exception (TaskCanceledException inside Task.Run). The question is now why does Task.Run use an Exception to control successful cancellation instead of just returning a completed Task. Is there any specific reason MS did not stick to the "Do NOT use exceptions to control execution flow" rule?.
And how can i avoid Boxing every method that supports cancellation (which are a lot) in an completely useless try catch (TaskCancelledException) block?
Well, you can't really see the difference in your very simple scenario - you're not actually using the result of the Task, and you don't need to propagate the cancellation through a complex call stack.
First, your Task might return a value. What do you return when the operation was cancelled?
Second, there may be other tasks that follow your cancelled task. You probably want to propagate the cancellation through the other tasks at your convenience.
Exceptions propagate. Task cancellation is pretty much identical to Thread.Abort in this usage - when you issue a Thread.Abort, a ThreadAbortException is used to make sure you unwind all the way back to the top. Otherwise, all of your methods would have to check the result of every method they call, check if they were cancelled, and return themselves if needed - and we've already seen that people will ignore error return values in old-school C :)
In the end, task cancellation, just like thread aborts, is an exceptional scenario. It already involves synchronization, stack unwinding etc.
However, this doesn't mean you necessarily have to use try-catch to catch the exception - you can use task states. For example, you can use a helper function like this:
public static Task<T> DefaultIfCanceled<T>(this Task<T> #this, T defaultValue = default(T))
{
return
#this.ContinueWith
(
t =>
{
if (t.IsCanceled) return defaultValue;
return t.Result;
}
);
}
Which you can use as
await SomeAsync().DefaultIfCanceled();
Of course, it should be noted that noöne is forcing you to use this method of cancellation - it's simply provided as a convenience. For example, you could use your own amplified type to preserve the cancellation information, and handle the cancellation manually. But when you start doing that, you'll find the reason why cancellation is handled using exceptions - doing this in imperative code is a pain, so you'll either waste a lot of effort for no gain, or you'll switch to a more functional way of programming (come, we have cookies!*).
(*) Disclaimer: We don't actually have cookies. But you can make your own!
The exception is thrown for a purpose as others in the community have already pointed it out.
However, if you would like to have more control over TaskCanceledException behaviour and still have the logic isolated to one place you may implement an Extension method to extend Task which handles cancellation, something like this -
public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
while (true)
{
if (cancellationToken.IsCancellationRequested) break;
//do some work
}
}).
WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run
}
static class TaskCacellationHelper
{
private struct Void { } // just to support TaskCompletionSource class.
public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion)
{
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask))
{
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task)
{
//
if (suppressCancellationExcetion == false)
{
ct.ThrowIfCancellationRequested();
}
else
{
Console.WriteLine("Cancelled but exception supressed");
}
}
}
// await original task. Incase of cancellation your logic will break the while loop
await originalTask;
}
}
Consider this code:
public IEnumerable<string> GetAmazonMwsNotifications(ScAmazonNotificationType scAmazonNotificationType, CancellationToken cancellationToken)
{
var scAmazonSqsMwsNotificationsManagmentClientRequestBuilder = _scServiceLocator.GetInstance<IScAmazonSqsMwsNotificationsManagmentClientRequestBuilder>();
var blockingCollection = new BlockingCollection<string>();
try
{
StartReceiveMessagesAsync(blockingCollection, cancellationToken, scAmazonNotificationType, scAmazonSqsMwsNotificationsManagmentClientRequestBuilder);
}
catch (Exception exception)
{
throw //this catch is never called;
}
return blockingCollection.GetConsumingEnumerable(cancellationToken);
}
private async void StartReceiveMessagesAsync(BlockingCollection<string> blockingCollection, CancellationToken cancellationToken, ScAmazonNotificationType scAmazonNotificationType, IScAmazonSqsMwsNotificationsManagmentClientRequestBuilder scAmazonSqsMwsNotificationsManagmentClientRequestBuilder)
{
var semaphore = new SemaphoreSlim(15);
var receiveMessageRequest = scAmazonSqsMwsNotificationsManagmentClientRequestBuilder.BuildReceiveMessageRequest(scAmazonNotificationType);
while (!cancellationToken.IsCancellationRequested)
{
await semaphore.WaitAsync(cancellationToken);
Task.Factory.StartNew(() =>
{
try
{
throw new ApplicationException("Test");
var receiveMessageResponse = _scAmazonSqsClientWrapper.ReceiveMessageAsync(receiveMessageRequest, cancellationToken).Result;
foreach (var result in receiveMessageResponse.Messages.Select(p => p.Body))
{
blockingCollection.Add(result, cancellationToken);
}
var deleteFromQueueRequest = scAmazonSqsMwsNotificationsManagmentClientRequestBuilder.BuildBatchDeleteMessageRequest(scAmazonNotificationType, receiveMessageResponse.Messages.Select(p => p.ReceiptHandle).ToArray());
_scAmazonSqsClientWrapper.DeleteMessageBatchAsync(deleteFromQueueRequest, cancellationToken);
}
finally
{
semaphore.Release(1);
}
}, cancellationToken, TaskCreationOptions.LongRunning | TaskCreationOptions.AttachedToParent, new ThreadPerTaskScheduler());
}
}
If the exception is thrown inside the task delegate it's never propagated to the calling method. I can't await for task inside the semaphore, because in this case semaphore will be blocked by the awaited task. Is there any way to propagate the exception to the calling method.
You're running in two problems:
async void has a slightly different error handling in comparision to traditional void methods: Although StartReceiveMessagesAsync is called by GetAmazonMwsNotifications and it blocks GetAmazonMwsNotifications until the first await (on an uncompleted Task) is reached, any exceptions from within StartReceiveMessagesAsync are never thrown back to GetAmazonMwsNotifications. In non UI applications they are always thrown onto the threadpool, bringing the application down (I don't know how UI applications work in that case).
So why doesn't your application die?
The exception is not thrown onto the stack, it's set onto the Task that is returned by Task.Factory.StartNew and this Task is not observed (neither via await nor via .Wait()). At some point the Garbage Collector will run to collect that Task and at that point an UnobservedTaskException will be thrown on the appdomain. When this is not observed your application will finally go down.
In my opinion you don't need to offload the code via Task.Run/Task.Factory.StartNew, await the result of ReceiveMessageAsync instead of blocking on it and handle exceptions in the async void method the "usual" way; just keep in mind that unhandled exceptions will bring down the application.
You can continue with another task with the OnlyOnFaulted option (i.e. an exception has been thrown and the task is in the faulted state).
.ContinueWith(t => { Console.WriteLine(t.Exception.Message); },
TaskContinuationOptions.OnlyOnFaulted);
Is it possible to catch when any Task terminates due exception and log? I've added CurrentDomain_UnhandledException handling but this doesn't help.
I create tasks using Task.Factory.StartNew() as usual. When somewhere inside such task exception occurs it crashes silently (but it supposed to work forever, i'm also using LongRunning option). So I want to be notified about such behavior.
Ideallly I want to set some option somewhere to be notified when any Task crashes due exception.
If it is not possible then likely I should add something to each Task I create? Of course I can just add big try{} finally{} block inside each Task, but probably there are better solutions?
Assuming you have a Test as Task to run:
static int Test()
{
throw new Exception();
}
First Approach - Process exception in the caller's thread:
Task<int> task = new Task<int>(Test);
task.Start();
try
{
task.Wait();
}
catch (AggregateException ex)
{
Console.WriteLine(ex);
}
Note: The exception will be of type AggregateException. All actual exceptions are available through ex.InnerExceptions property.
Second Approach - Process exception in some task's thread:
Define the ExceptionHandler this way:
static void ExceptionHandler(Task<int> task)
{
var ex = task.Exception;
Console.WriteLine(ex);
}
Usage:
Task<int> task = new Task<int>(Test);
task.ContinueWith(ExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
task.Start();
Reference: How to: Handle Exceptions Thrown by Tasks
For tasks that you create yourself, it's reasonably simple: create your own methods which call Task.Factory.StartNew(), but then also call Task.ContinueWith(loggingDelegate, TaskContinuationOptions.OnlyOnFaulted before returning the task.
The problem is that that won't add a fault handler for tasks created by other bits of infrastructure - including by async methods in C# 5. It still might be useful to you though.
You can also use TaskScheduler.UnobservedTaskException, but as per the name that will only be called for exceptions which aren't already observed by something else. (Again, that may be fine for you...)
You can use an extension method that performs an operation when an exception has ocurred.
This happens when the Task gets Faulted. So if it has another tasks to continue with, the next one can check if the previous task was faulted and Log the exception.
I usually use this methods:
//If you want to chain more tasks..
public static Task<T> Continue<T>(this Task<T> task, Action<T> action)
{
if (!task.IsFaulted)
{
task.ContinueWith((t) => action(t.Result), TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion);
}
return task;
}
public static Task OnException(this Task task, Action<Exception> onFaulted)
{
task.ContinueWith(c =>
{
var excetion = c.Exception;
onFaulted(excetion);
},
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
return task;
}
So you can use:
Task.Factory.StartNew(...).OnException(ex => Log(ex));
Hope it helps.
Wrap your task.Wait() in a try/catch block and catch AggregateException. Something like this -
Task<string[]> task1 = Task<string[]>.Factory.StartNew(() => GetAllFiles(path));
// Use this line to throw an exception that is not handled.
try
{
task1.Wait();
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
if (x is UnauthorizedAccessException) // This we know how to handle.
{
Console.WriteLine("You do not have permission to access all folders
in this path.");
Console.WriteLine("See your network administrator or try
another path.");
return true;
}
return false; // Let anything else stop the application.
});
}
Details can be found here - Handle exceptions thrown by Task.
You can create a OnlyOnFaulted continuation on your Task which observes the exception and logs/reports the problem.
t.ContinueWith(task =>
{
// Report and log error
}, System.Threading.CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext());
The above code will run the task on the UI thread because of TaskScheduler.FromCurrentSynchronizationContext(). This may be necessary if you are using winforms and need to notify the user.