I'm working on a large monolith web application running on ASP.NET framework and added a feature. I've build some abstraction in order to run several tasks in parallel.
public interface IImporter
{
Action[] GetProcessActions();
}
public class ImportTaskFactory : IDisposable
{
private readonly Task[] _tasks;
/// <summary>
/// Immediately starts all the Process tasks
/// </summary>
public ImportTaskFactory(CancellationToken cancellationToken, Action<Task> onTaskFaultedAction, IEnumerable<IImporter> importers)
{
_tasks = importers
.SelectMany(importer =>
importer
.GetProcessActions()
.Select(action =>
Task.Factory.StartNew(action, cancellationToken)
.ContinueWith(onTaskFaultedAction, TaskContinuationOptions.OnlyOnFaulted)))
.ToArray();
}
public void WaitAll(CancellationToken cancellationToken)
{
Task.WaitAll(_tasks, cancellationToken);
}
public void Dispose()
{
foreach (var task in _tasks)
{
task?.Dispose();
}
}
}
and it's called like
//...
var importers = new IImporter[]
{
//...three classes deriving from IImporter
}
using (var importTasks =
new ImportTaskFactory(_cancellationTokenSource.Token, OnTaskFaulted, importers))
{
importTasks.WaitAll(_cancellationTokenSource.Token);
}
//... where:
private void OnTaskFaulted(Task task)
{
Log.Logline(task.Exception.ToString());
_cancellationTokenSource.Cancel();
}
//...
However, when running the tasks start out fine and do their work, but seem to stop for unclear reason, each throwing an System.Threading.Tasks.TaskCanceledException
(no inner exception).
If I look at the passed CancellationToken
, cancellation was not requested.
I've been trying to debug this for a couple of hours, but have no idea what's happening. How can the tasks get cancelled this way? How can I debug this? I mean how can I see what triggers the cancellation?
I've set breakpoints in the OnTaskFaulted
method, but it never seems to be called... That's the only point where _cancellationTokenSource.Cancel();
is called.
CodePudding user response:
This not an answer to your question. It just shows how to do what you want, without getting these pesky cancellation exceptions:
void ImportTaskFactory(CancellationToken cancellationToken,
Action<Task> onTaskFaultedAction, IEnumerable<IImporter> importers)
{
_tasks = importers
.SelectMany(importer => importer
.GetProcessActions()
.Select(action => Task.Run(() =>
{
try
{
action();
}
catch (Exception ex)
{
onTaskFaultedAction(Task.FromException(ex));
}
}, cancellationToken))).ToArray();
}
I replaced the Task.Factory.StartNew
with Task.Run
, because the former requires to pass explicitly the scheduler
every time you use it.
Probably it would be simpler if the onTaskFaultedAction
parameter was of type Action<Exception>
instead of Action<Task>
.