我有几个要执行的任务。每个任务在不同的持续时间内完成执行。一些任务执行数据库访问,其中一些只是进行一些计算。我的代码具有以下结构:
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个任务尚未完成。然后我必须取消这两项任务。如果我试图处理它们,我会得到一个例外,说“处于完成状态的任务只能处理掉。”
我怎样才能做到这一点?在我调用Cancel
CancellationTokenSource
方法后,这两项任务仍然执行。这有什么不对?
答案 0 :(得分:4)
首先,你几乎不应该使用Task.Start
。请改用静态Task.Run
方法。
当您将CancellationToken
传递给Task.Run
或其他创建任务的API时,这不允许您通过请求取消立即中止任务。如果任务中的代码抛出Canceled
异常,这只会将任务的状态设置为OperationCanceledException
。请查看this article的CancellationToken部分。
要取消任务,任务运行的代码必须与您合作。例如,如果代码在循环中执行某些操作,则该代码必须定期检查是否请求取消,如果是,则抛出异常(或者如果您不希望将任务视为已取消,则只需退出循环)。 CancellationToken
中有一种名为ThrowIfCancellationRequested
的方法可以做到这一点。这当然意味着此类代码需要访问CancellationToken
对象。这就是为什么我们有接受取消令牌的方法。
作为另一个示例,如果任务运行的代码调用数据库访问方法,则最好调用接受CancellationToken
的方法,以便在请求取消后,此类方法将尝试退出。
总而言之,取消操作并不是一件神奇的事情,因为任务运行的代码需要合作。
答案 1 :(得分:2)
如果您想要取消尚未完成的任务,则需要通过合作取消来完成。目前,您的任务都不会监控传递给它们的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);
}
如果无法使用async,请考虑在之后使用Thread.Sleep
监控给定的令牌,以便您的线程知道您确实要求取消。
旁注:
Task.Run
代替new Task
。前者返回一个热门任务&#34;已经开始,无需迭代集合并调用Start
。Task
。仅在您使用WaitHandle
公开的Task
时才使用它,而您在此处未使用Task.WhenAll
代替Task.WaitAll
。答案 2 :(得分:0)
在Task类中,取消涉及用户委托(代表可取消操作)和请求取消的代码之间的协作。成功取消涉及请求代码调用CancellationTokenSource.Cancel()
方法,并且用户委托及时终止操作。您可以使用以下选项之一终止操作:
只需从代表返回即可。在许多情况下,这已足够;但是,以这种方式取消的任务实例将转换为TaskStatus.RanToCompletion
状态,而不是转换为TaskStatus.Canceled状态。
通过抛出OperationCanceledException
并传递请求取消的令牌。执行此操作的首选方法是使用ThrowIfCancellationRequested()
方法。以这种方式取消的任务转换为Canceled状态,调用代码可以使用该状态来验证任务是否响应了其取消请求。
因此,您必须在任务中侦听取消信号:
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;