Reputation: 4929
I have a wrapper to the Task.Factory.StartNew, called TaskManager, that I needed to implement in order to add exception handling in the new thread. Here is my class:
public static class TaskManager
{
public static Task StartNew(Action action, IUserContextBase userContext, Action<Task> onCompletedCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action), userContext, null, onCompletedCallback);
}
public static Task StartNew(Action action, IUserContextBase userContext, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, TaskScheduler taskScheduler, Action<Task> onCompletedCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action, cancellationToken, taskCreationOptions, taskScheduler), userContext, null, onCompletedCallback);
}
public static Task StartNew(Action action, IUserContextBase userContext, TaskCreationOptions taskCreationOptions, Action<Task> onCompletedCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action, taskCreationOptions), userContext, null, onCompletedCallback);
}
public static Task StartNew(Action<object> action, IUserContextBase userContext, object state, Action<Task> onCompletedCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action, state), userContext, null, onCompletedCallback);
}
public static Task StartNew(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions taskCreationOptions, TaskScheduler taskScheduler, Action<Task> onCompletedCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action, null, cancellationToken, taskCreationOptions, taskScheduler), null, null, onCompletedCallback);
}
public static Task StartNew(Action<object> action, object state, Action<System.Exception> onExceptionCallback = null, Action<Task> onCompletedCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action, state), null, onExceptionCallback, onCompletedCallback);
}
public static Task StartNew(Action action, Action<System.Exception> onExceptionCallback = null)
{
return AddContinueWithToTask(Task.Factory.StartNew(action), null, onExceptionCallback);
}
public static Task<TResult> StartNew<TResult>(Func<TResult> function, IUserContextBase userContext, CancellationToken cancellationToken)
{
return Task.Factory.StartNew(function, cancellationToken).ContinueWith(t =>
{
ManageException(t, userContext);
return t.Result;
}, TaskContinuationOptions.OnlyOnFaulted);
}
private static void ManageException(Task t, IUserContextBase userContext, Action<System.Exception> onExceptionCallback = null)
{
if (t.Exception != null)
if (onExceptionCallback != null)
onExceptionCallback(t.Exception);
else
t.Exception.Handle(ex =>
{
GlobalContainer.Unity.Resolve<IDiagnosticLogHandler>().LogError(userContext, ex, ErrorSeverity.Normal);
return true;
});
}
private static Task AddContinueWithToTask(Task task, IUserContextBase userContext, Action<System.Exception> onExceptionCallback = null, Action<Task> onCompletedCallback = null)
{
return task
.ContinueWith(t => ManageException(t, userContext, onExceptionCallback), TaskContinuationOptions.OnlyOnFaulted)
.ContinueWith(t2 => { if (onCompletedCallback != null) onCompletedCallback(t2); })
.ContinueWith(t => ManageException(t, userContext, onExceptionCallback), TaskContinuationOptions.OnlyOnFaulted);
}
However, whenever someone uses my class (it returns a Task) and then calls task.Wait() or task.WaitAll() afterwards, they seem to get an exception saying that the tasks have (all) been cancelled. The error occurs at the Wait() or WaitAll(). What could be wrong with my TaskManager class?
Upvotes: 1
Views: 384
Reputation: 39277
The problem is that you are chaining your t2
task onto the end of a task which never runs because it is set to OnlyOnFaulted
.
Try this:-
private static Task AddContinueWithToTask(Task task, object userContext, Action<System.Exception> onExceptionCallback = null, Action<Task> onCompletedCallback = null)
{
return task
.ContinueWith(t => {
if (t.IsFaulted)
ManageException(t, userContext, onExceptionCallback);
})
.ContinueWith(t2 =>
{
if (onCompletedCallback != null) onCompletedCallback(t2);
})
.ContinueWith(t => {
if (t.IsFaulted)
ManageException(t, userContext, onExceptionCallback);
})
;
}
From the MSDN on Task.Continuewith
The returned Task will not be scheduled for execution until the current task has completed. If the criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
Upvotes: 0
Reputation: 73442
You're actually returning the continuation of the original task to the caller not the actual task.
Your continuation has TaskContinuationOptions.OnlyOnFaulted
, which means run it only when antecedent is faulted, otherwise cancel it. And hence you see the caller gets TaskCancelledException
.
In other words, When actual task Run to completion, Continuation will be cancelled. When actual task is faulted, Continuation will Run to completion given that TaskContinuationOptions.OnlyOnFaulted
is used.
To fix it, you need to return the actual task to the caller but add the continuation and keep it with you.
Update: To return the original task, just use a local variable.
public static Task StartNew(Action action, Action<System.Exception> onExceptionCallback = null)
{
var actualTask = Task.Factory.StartNew(action);
AddContinueWithToTask(actualTask, null, onExceptionCallback);
return actualTask;
}
...
Upvotes: 2