[英]C# tasks are not cancelled
我有几个任务要执行。 每个任务在不同的持续时间内完成其执行。 一些任务执行数据库访问,其中一些只是进行一些计算。 我的代码具有以下结构:
var Canceller = new CancellationTokenSource();
List<Task<int>> tasks = new List<Task<int>>();
tasks.Add(new Task<int>(() => { Thread.Sleep(3000); Console.WriteLine("{0}: {1}", DateTime.Now, 3); return 3; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(1000); Console.WriteLine("{0}: {1}", DateTime.Now, 1); return 1; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(2000); Console.WriteLine("{0}: {1}", DateTime.Now, 2); return 2; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(8000); Console.WriteLine("{0}: {1}", DateTime.Now, 8); return 8; }, Canceller.Token));
tasks.Add(new Task<int>(() => { Thread.Sleep(6000); Console.WriteLine("{0}: {1}", DateTime.Now, 6); return 6; }, Canceller.Token));
tasks.ForEach(x => x.Start());
bool Result = Task.WaitAll(tasks.Select(x => x).ToArray(), 3000);
Console.WriteLine(Result);
Canceller.Cancel();
tasks.ToList().ForEach(x => { x.Dispose(); }); // Exception here
tasks.Clear();
tasks = null;
Canceller.Dispose();
Canceller = null;
我有5秒钟的时间来启动所有这些任务。 每隔5秒钟我就会调用上面的代码。 在下一次调用之前,我必须确保上一个执行周期没有剩余任务。 假设执行后经过3秒钟,我想取消未完成的任务的执行。
当我运行代码Task.WaitAll
3000时,使前3个任务按预期完成。 然后我得到的Result
为false
因为其他2个任务尚未完成。 然后,我必须取消这两个任务。 如果我要处置它们,我会说“只能完成处置状态的任务”。
我该如何实现? 在我调用CancellationTokenSource
Cancel
方法之后,这两个任务仍然执行。 怎么了
首先,几乎应该永远不要使用Task.Start
。 请改用静态Task.Run
方法。
当您将CancellationToken
传递给Task.Run
或其他创建任务的API时,这不允许您通过请求取消立即中止任务。 仅当任务中的代码引发OperationCanceledException
异常时,才将任务的状态设置为“ Canceled
OperationCanceledException
。 请查看本文的CancellationToken部分。
要取消任务,任务运行的代码必须与您配合。 例如,如果代码在循环中执行了某些操作,则该代码必须定期检查是否请求取消,如果是,则抛出异常(或者,如果您不希望任务被视为取消,则直接退出循环)。 在CancellationToken
有一个名为ThrowIfCancellationRequested
的方法可以做到这一点。 当然,这意味着此类代码需要访问CancellationToken
对象。 这就是为什么我们有一些接受取消标记的方法的原因。
作为另一个示例,如果任务运行的代码调用数据库访问方法,则最好调用一个接受CancellationToken
的方法,以便该方法将在请求取消时立即尝试退出。
因此,总而言之,取消操作并不是一件神奇的事情,因为任务运行的代码需要配合。
如果要取消尚未完成的任务,则需要通过协作取消来取消。 当前,您的任何任务都无法监视传递给他们的CancellationToken
。
如果您从睡眠中唤醒后监视令牌,则可以使用同步Thread.Sleep
监视令牌。这不会使当前处于睡眠状态的任何正在进行的线程中止。 相反,我使用Task.Delay
提供了一种替代方法。 当您要监视令牌时,这是合适的,因为它允许您将令牌传递给延迟操作本身。
异步等效项的粗略草图如下所示:
public async Task ExecuteAndTimeoutAsync()
{
var canceller = new CancellationTokenSource();
var tasks = new[]
{
Task.Run(async () =>
{
var delay = 2000;
await Task.Delay(delay, canceller.Token);
if (canceller.Token.IsCancellationRequested)
{
Console.WriteLine($"Operation with delay of {delay} cancelled");
return -1;
}
Console.WriteLine("{0}: {1}", DateTime.Now, 3);
return 3;
}, canceller.Token),
Task.Run(async () =>
{
var delay = 5000;
await Task.Delay(, canceller.Token);
if (canceller.Token.IsCancellationRequested)
{
Console.WriteLine($"Operation with delay of {delay} cancelled");
return -1;
}
Console.WriteLine("{0}: {1}", DateTime.Now, 2);
return 2;
}, canceller.Token)
};
await Task.Delay(3000);
canceller.Cancel();
await Task.WhenAll(tasks);
}
如果无法使用异步,请在使用Thread.Sleep
之后考虑对给定令牌进行监视,以便您的线程知道您实际上已请求取消。
边注:
Task.Run
而不是new Task
。 前者返回一个已经启动的“热门任务”,无需迭代集合并调用Start
。 Task
。 仅当您使用Task
公开的WaitHandle
时才使用它 Task.WhenAll
而不是Task.WaitAll
。 在Task类中,取消涉及代表可取消操作的用户委托与请求取消的代码之间的合作。 成功的取消涉及请求代码调用CancellationTokenSource.Cancel()
方法,以及用户委托及时终止操作。 您可以使用以下选项之一终止操作:
通过简单地从代表那里返回。 在许多情况下,这就足够了; 但是,以这种方式取消的任务实例将转换为TaskStatus.RanToCompletion
状态,而不是TaskStatus.Canceled状态。
通过抛出OperationCanceledException
并向其传递请求取消的令牌。 执行此操作的首选方法是使用ThrowIfCancellationRequested()
方法。 以这种方式取消的任务将转换为“取消”状态,调用代码可以使用该状态来验证任务是否响应了其取消请求。
因此,您必须在任务中监听取消信号:
var Canceller = new CancellationTokenSource();
var token = Canceller.Token;
List<Task<int>> tasks = new List<Task<int>>();
tasks.Add(new Task<int>(() => { Thread.Sleep(3000); token.ThrowIfCancellationRequested(); Console.WriteLine("{0}: {1}", DateTime.Now, 3); return 3; }, token));
tasks.Add(new Task<int>(() => { Thread.Sleep(1000); token.ThrowIfCancellationRequested(); Console.WriteLine("{0}: {1}", DateTime.Now, 1); return 1; }, token));
tasks.Add(new Task<int>(() => { Thread.Sleep(2000); token.ThrowIfCancellationRequested(); Console.WriteLine("{0}: {1}", DateTime.Now, 2); return 2; }, token));
tasks.Add(new Task<int>(() => { Thread.Sleep(8000); token.ThrowIfCancellationRequested(); Console.WriteLine("{0}: {1}", DateTime.Now, 8); return 8; }, token));
tasks.Add(new Task<int>(() => { Thread.Sleep(6000); token.ThrowIfCancellationRequested(); Console.WriteLine("{0}: {1}", DateTime.Now, 6); return 6; }, token));
tasks.ForEach(x => x.Start());
bool Result = Task.WaitAll(tasks.Select(x => x).ToArray(), 3000);
Console.WriteLine(Result);
Canceller.Cancel();
try
{
Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ex)
{
if (!(ex.InnerException is TaskCanceledException))
throw ex.InnerException;
}
tasks.ToList().ForEach(x => { x.Dispose(); });
tasks.Clear();
tasks = null;
Canceller.Dispose();
Canceller = null;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.