Sha*_*ani 2 c# linq parallel.foreach cancellation-token
考虑这个简单的示例代码:
var cts = new CancellationTokenSource();
var items = Enumerable.Range(1, 20);
var results = items.AsParallel().WithCancellation(cts.Token).Select(i =>
{
double result = Math.Log10(i);
return result;
});
try
{
foreach (var result in results)
{
if (result > 1)
cts.Cancel();
Console.WriteLine($"result = {result}");
}
}
catch (OperationCanceledException e)
{
if (cts.IsCancellationRequested)
Console.WriteLine($"Canceled");
}
Run Code Online (Sandbox Code Playgroud)
对于并行结果中的每个结果,它会打印结果,直到 result > 1
此代码输出类似于:
result = 0.9030899869919435
result = 0.8450980400142568
result = 0.7781512503836436
result = 0
result = 0.6020599913279624
result = 0.47712125471966244
result = 0.3010299956639812
result = 0.6989700043360189
result = 0.9542425094393249
result = 1
result = 1.0413926851582251 <-- This is normal
result = 1.2041199826559248 <-- Why it prints this value (and below)
result = 1.0791812460476249
result = 1.2304489213782739
result = 1.1139433523068367
result = 1.255272505103306
result = 1.146128035678238
result = 1.2787536009528289
result = 1.1760912590556813
result = 1.3010299956639813
Canceled
Run Code Online (Sandbox Code Playgroud)
我的问题是为什么它继续打印超过 1 的值?我原以为Cancel()令牌将终止该过程。
更新 1
@mike-s的回答建议:
检查循环内的取消标记(作为中止循环的一种方式)或在长时间操作之前也很有用。
我试过添加支票
foreach (var result in results)
{
if (result > 1)
cts.Cancel();
if (!cts.IsCancellationRequested) //<----Check the cancellation token before printing
Console.WriteLine($"result = {result}");
}
Run Code Online (Sandbox Code Playgroud)
它仍然给出相同的结果输出。
Eri*_*ert 10
我的问题是为什么它继续打印超过 1 的值?
想象一下,您雇佣了 100 名飞行员从 100 个机场驾驶 100 架飞机。一群人起飞,然后你发送一条消息说“取消所有航班”。好吧,当您发送该消息时,跑道上有许多飞机以起飞速度在起飞,并且在它们升空后消息才到达。这些航班不会被取消!
您正在发现有关多线程编程的最重要的知识。你必须推理好像所有可能发生的事情的顺序都可能发生。这包括比您认为应该到达的消息晚。
特别是,您的问题是您滥用并行化机制的结果,这些机制旨在并行化长时间工作。您已经创建了一堆任务,这些任务的运行时间比发送停止它们的消息所需的时间要少。在这种情况下,某些任务在被告知停止后完成也就不足为奇了。
我预计在令牌上调用 Cancel() 会终止该过程。
你的期望是完全错误的。不要再期望了,因为这种期望与现实完全不符。取消令牌是在方便时立即取消操作的请求。它不是终止线程或进程。
但是,即使您确实终止了线程,您仍然会观察到这种行为。线程终止与任何其他事件一样是一个事件,并且该事件不是瞬时的。执行需要时间,而其他线程可以在该线程终止执行时继续其工作。
“请求在方便时立即取消操作”中的“方便”是什么意思?
让我们退后一步。
如果要完成的工作非常短,则无需将其表示为任务。干活吧!一般来说,如果工作时间少于大约 30 毫秒,只需完成工作即可。
因此,让我们假设每个任务都需要很长时间。
现在,为什么一项任务可能需要很长时间?一般有两个原因:
我们正在等待另一个系统完成一些任务。我们正在等待网络数据包或磁盘读取或诸如此类的事情。
我们的计算量很大,CPU 已经饱和了。
假设我们处于第一种情况。并行化有帮助吗?不。如果您正在等待邮寄包裹,雇佣一个、两个、十个或一百人等待并不会使包裹更快送达。
但这对第二种情况确实有帮助;如果我们在机器中有一个额外的 CPU,我们可以专用于两个在大约一半的时间内CPU 来解决问题。
因此我们可以假设,如果我们正在并行化一个任务,那是因为 CPU 正在做很多工作。
伟大的。现在,“CPU 做了很多工作”的本质是什么?它几乎总是涉及某个地方的循环。
那么,我们如何取消一个任务呢?我们不会通过终止线程来取消任务。我们要求任务自行取消。一个设计良好的任务将接受一个取消标记,并在其循环中检查取消标记是否表明任务已被取消。 取消是合作的。任务必须合作并决定何时检查它是否被取消。
请注意,检查您是否被取消是一项工作,而且这项工作会占用实际任务的时间。如果您花一半时间检查自己是否被取消,那么您的任务将花费两倍的时间。请记住,并行化任务的重点是让它花费一半的时间,因此将完成任务所需的时间加倍是不可能的。
因此,大多数任务不会每次都通过循环检查它们是否被取消。精心设计的任务将每隔几毫秒检查一次,而不是每隔几纳秒。
这就是我所说的“取消是在方便时要求停止”的意思。如果任务编写正确,则应该知道检查取消的好时机是什么,以便平衡响应性与性能。
| 归档时间: |
|
| 查看次数: |
371 次 |
| 最近记录: |