C# 取消令牌源和嵌套任务
我对我正在使用的取消令牌源有疑问,如下代码所示:C# 取消令牌源和嵌套任务,c#,.net,multithreading,task-parallel-library,task,C#,.net,Multithreading,Task Parallel Library,Task,我对我正在使用的取消令牌源有疑问,如下代码所示: void Process() { //for the sake of simplicity I am taking 1, in original implementation it is more than 1 var cancellationToken = _cancellationTokenSource.Token; Task[] tArray = new Task[1];
void Process()
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
var cancellationToken = _cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
MainTaskRoutine();
}, cancellationToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
void MainTaskRoutine()
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
//this method shows that a nested task is created
var cancellationToken = _cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
}, cancellationToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
编辑:进一步细化 最终目标是:当用户取消操作时,所有立即挂起的任务(子任务或孙子任务)都应取消 场景: 根据上述代码: 1.我首先检查用户是否要求取消 2.如果用户未要求取消,则仅继续执行任务(请参阅处理方法)。 示例代码在这里只显示一个任务,但实际上可以有三个或更多个任务 假设CPU开始处理Task1,而其他任务仍在任务队列中等待一些CPU来执行它们。 用户请求取消:进程方法中的任务2、3将立即取消,但任务1将继续工作,因为它已在进行处理 在Task1中,它调用MainTaskRoutine方法,这反过来会创建更多任务 在MainTaskRoutine的函数中,我编写了:cancellationToken.ThrowIfCancellationRequested()
所以问题是:使用CancellationTokenSource是否正确,因为它依赖于Task.WaitAll()?[EDITED]当您在代码中使用数组时,我假设可能有多个任务,而不仅仅是一个任务。我还假设,在从
进程开始的每个任务中,您希望首先执行一些CPU限制的工作(//在此处执行一些工作
),然后运行MainTaskRoutine
)
处理任务取消异常的方式由项目设计工作流决定。例如,您可以在Process
方法内部执行,也可以从调用Process
的位置执行。如果您唯一关心的是从跟踪挂起任务的数组中删除任务对象,那么可以使用。无论任务的完成状态如何(Cancelled
、Faulted
或RanToCompletion
),都将继续执行:
做了一些研究后我发现
代码现在如下所示:
请参阅下面代码中CancellationTokenSource.CreateLinkedTokenSource的用法
注意:我没有使用它,但一旦使用完毕,我会立即通知您:)注意Task.Run
将打开一个嵌套的任务,因此从Task.Run
返回的任务实际上是由mainstaskroutine
返回的任务。还要注意TaskContinuationOptions.ExecuteSynchronously
,它用于确保在触发所有之前执行继续清理代码。我相信我发布的代码运行正常。所有创建的任务共享相同的cancellationToken
。只要在每个任务中定期调用cancellationToken.ThrowIfCancellationRequested()
,任何挂起的任务都将被取消<代码>进程
和主任务例程
本身不执行任何CPU限制的任务,因此它们将在其所有子任务完成后立即取消(whalll
)。但是,您可以在过程的最开始添加cancellationToken.ThrowIfCancellationRequested()
和MainTaskRoutine
,以确保它们不会启动任何子任务。如果我继续将CancellationTokenSource作为参数传递给任务,可以吗,因此,如果任务需要创建任何子任务,它可能会使用此参数?根据您的编辑:最终目标是:当用户取消操作时,所有立即挂起的任务(子任务或子任务)都应取消。我不太明白你说的“立即”是什么意思。您只想取消由进程创建的任务(而不是由MainTaskRoutine
)吗?是的,您可以(也应该)将相同的CancellationTokenSource.Token
传递给任意多个任务,假设调用CancellationTokenSource时逻辑上应取消所有这些项。对该CancellationTokenSource
对象取消。为什么需要新的链接取消源?无论如何,我没有看到你在上面调用Cancel()
。在我看来,在CreateLinkedTokenSource
@nosratio中使用至少两个令牌是有意义的。根据我的问题,是用户将取消即时任务,这些任务将触发这些“MainTaskRoutine”任务。总之,它将为所有未决任务产生连锁反应。希望有意义。取消的顺序并不重要,一个原始的取消令牌就足以取消所有任务。
Task Process(CancellationToken cancellationToken)
{
var tArray = new List<Task>();
var tArrayLock = new Object();
var task = Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
return MainTaskRoutine(cancellationToken);
}, cancellationToken);
// add the task to the array,
// use lock as we may remove tasks from this array on a different thread
lock (tArrayLock)
tArray.Add(task);
task.ContinueWith((antecedentTask) =>
{
if (antecedentTask.IsCanceled || antecedentTask.IsFaulted)
{
// handle cancellation or exception inside the task
// ...
}
// remove task from the array,
// could be on a different thread from the Process's thread, use lock
lock (tArrayLock)
tArray.Remove(antecedentTask);
}, TaskContinuationOptions.ExecuteSynchronously);
// add more tasks like the above
// ...
// Return aggregated task
Task[] allTasks = null;
lock (tArrayLock)
allTasks = tArray.ToArray();
return Task.WhenAll(allTasks);
}
// handle the completion asynchronously with a blocking wait
void RunProcessSync()
{
try
{
Process(_cancellationTokenSource.Token).Wait();
MessageBox.Show("Process complete");
}
catch (Exception e)
{
MessageBox.Show("Process cancelled (or faulted): " + e.Message);
}
}
// handle the completion asynchronously using ContinueWith
Task RunProcessAync()
{
return Process(_cancellationTokenSource.Token).ContinueWith((task) =>
{
// check task.Status here
MessageBox.Show("Process complete (or cancelled, or faulted)");
}, TaskScheduler.FromCurrentSynchronizationContext());
}
// handle the completion asynchronously with async/await
async Task RunProcessAync()
{
try
{
await Process(_cancellationTokenSource.Token);
MessageBox.Show("Process complete");
}
catch (Exception e)
{
MessageBox.Show("Process cancelled (or faulted): " + e.Message);
}
}
void Process()
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
var cancellationToken = _cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
MainTaskRoutine(cancellationToken);
}, cancellationToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
void MainTaskRoutine(CancellationToken cancellationToken)
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
//this method shows that a nested task is created
using (var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
var cancelToken = cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancelToken.ThrowIfCancellationRequested();
//do some work here
}, cancelToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
}