方案是将各种任务排入队列,节制它们以进行并行处理,并能够取消它们。 我的问题是,由于所有任务已经挂在信号量上,取消它们实际上比任务本身需要更长的时间。
很明显,.Run将所有任务都放入.WaitAsync中,因此其状态为WaitingForActiviation。因此,赋予任务本身的令牌实际上是没有意义的:所有1000个任务已经在运行。
将令牌提供给WaitAsync似乎会使应用程序在取消时冻结。
static SemaphoreSlim batcher = new SemaphoreSlim(5);
static void Main(string[] args)
{
var tokenStore = new CancellationTokenSource();
var tasks = Enumerable.Range(1, 1000).Select(i =>
DoableWork(tokenStore.Token)
).ToList();
do {
if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape) {
Console.WriteLine("Cancelling tasks");
tokenStore.Cancel();
}
Thread.Sleep(1000);
Console.WriteLine($"Tasks: {string.Join(", ", tasks.GroupBy(t => t.Status).Select(x => $"{x.Count()} {x.Key}"))}");
} while (tasks.Any(t => t.Status < TaskStatus.RanToCompletion));
}
private static Task DoableWork(CancellationToken token)
{
return Task.Run(async () => {
try {
await batcher.WaitAsync();
token.ThrowIfCancellationRequested();
await Task.Delay(200, token); // Do stuff
} catch (OperationCanceledException) {
throw;
} catch (Exception) {
// Logging
throw;
} finally {
batcher.Release();
}
}, token);
}
结果:
Tasks: 25 RanToCompletion, 975 WaitingForActivation
Tasks: 50 RanToCompletion, 950 WaitingForActivation
Tasks: 75 RanToCompletion, 925 WaitingForActivation
Tasks: 100 RanToCompletion, 900 WaitingForActivation
Tasks: 120 RanToCompletion, 880 WaitingForActivation
Tasks: 145 RanToCompletion, 855 WaitingForActivation
Cancelling tasks
Tasks: 145 RanToCompletion, 16 Canceled, 839 WaitingForActivation
Tasks: 145 RanToCompletion, 33 Canceled, 822 WaitingForActivation
Tasks: 145 RanToCompletion, 51 Canceled, 804 WaitingForActivation
Tasks: 145 RanToCompletion, 66 Canceled, 789 WaitingForActivation
Tasks: 145 RanToCompletion, 81 Canceled, 774 WaitingForActivation
Tasks: 145 RanToCompletion, 101 Canceled, 754 WaitingForActivation
如您所见,每秒取消的任务少于处理的任务。以这种速度,您将等待一整分钟,以取消1000个待处理的任务。
使用新的Task()作为替代方案,并调用Task.Start()将导致取消取消机制,而不会引发每个正在运行的任务的未处理异常。
tasks.Where(t => t.Status < TaskStatus.Running)
.Take(5 - tasks.Count(t => t.Status == TaskStatus.Running))
.ToList()
.ForEach(t => t.Start());
private static Task DoableWork(CancellationToken token)
{
return new Task(async () => {
try {
await Task.Delay(200, token); // Do stuff
} catch (OperationCanceledException) {
throw;
} catch (Exception) {
// Logging
throw;
}
}, token);
}
答案 0 :(得分:1)
发现了问题。 VS 2019(或其设置之一)在调试模式下大大减慢了异常处理的速度。当我直接运行.exe时,取消是即时的。 感谢@TheodorZoulias显示无法重现该问题。