等待CancellationToken取消请求

use*_*177 1 c# task-parallel-library

如何在请求取消之前暂停执行?

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    // Wait for the Cancel...

    Console.WriteLine("Canceled!");
});

Console.ReadKey();

cts.Cancel();

Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

Ste*_*nio 12

在内部CancellationTokenSource使用 a ManualResetEvent,您可以等待公开WaitHandle暂停执行,直到它被设置。

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    WaitHandle.WaitAny(new[] { cts.Token.WaitHandle });

    Console.WriteLine("Canceled!");
});

Console.ReadKey();

cts.Cancel();

Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

这是在 中定义的 WaitHandle CancellationTokenSource

ManualResetEvent mre = new ManualResetEvent(false);
if (Interlocked.CompareExchange(ref m_kernelEvent, mre, null) != null)
{    
    ((IDisposable)mre).Dispose();
}

// There is a ---- between checking IsCancellationRequested and setting the event.
// However, at this point, the kernel object definitely exists and the cases are:
//   1. if IsCancellationRequested = true, then we will call Set()
//   2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set().
if (IsCancellationRequested)
    m_kernelEvent.Set();

return m_kernelEvent;
Run Code Online (Sandbox Code Playgroud)

并且Token只是从源返回句柄(有一个内部变量引用它)。

另一种选择是注册Token回调并使用您自己的ManualResetEvent

var cts = new CancellationTokenSource();

Task.Run(() =>
{
    var mre = new ManualResetEvent(false);

    var registration = cts.Token.Register(() => mre.Set());

    using (registration)
    {
        mre.WaitOne();

        Console.WriteLine("Canceled!");
    }
});

Console.ReadKey();

cts.Cancel();

Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

示例:https : //blogs.msdn.microsoft.com/pfxteam/2009/05/22/net-4-cancellation-framework/

  • 在链接中有WaitOne/WaitAny。在具有一个 WaitHandle 的数组上使用 WaitOne 还是 WaitAny 并不重要。这是相同的。 (2认同)
  • 链接中的@user4388177 正在等待两个句柄,在这里您可以使用`WaitOne`,我只是从帖子中复制了示例,并在没有“重构”的情况下对其进行了修改。 (2认同)

The*_*ias 11

最简洁的方式可能是这样的:

try { await Task.Delay(Timeout.Infinite, cts.Token); }
catch (OperationCanceledException) { } // Ignore this exception
Run Code Online (Sandbox Code Playgroud)

这种方法依赖于捕获异常,而异常的成本很高。如果您偶尔等待令牌,这应该不是问题,但如果您在紧密循环中等待令牌,则可能需要在项目中包含一个廉价的 s 等待者,如本文CancellationToken所述。它将允许您执行以下操作:

await cts.Token;
Run Code Online (Sandbox Code Playgroud)

替代方案:也可以使用此技巧来抑制异常:

await Task.WhenAny(Task.Delay(Timeout.Infinite, cts.Token));
Run Code Online (Sandbox Code Playgroud)

使用Task.WhenAny来抑制异常会对TaskScheduler.UnobservedTaskException事件产生一些影响,如本答案中所述。但它不会影响这种情况(事件未触发)。

  • 关于 UnobservedTaskException 的评论通常是有效的,但链接提到它不会为 *canceled* 任务触发。 (2认同)

Kev*_*sse 8

您可以使用WaitHandle等待同步:

static void Main()
{
    var cts = new CancellationTokenSource();

    Task.Run(() =>
    {
        // Wait for the Cancel...

        cts.Token.WaitHandle.WaitOne();

        Console.WriteLine("Canceled!");
    });

    Console.ReadKey();

    cts.Cancel();

    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

也就是说,"阻止线程直到某些东西被取消"是一种非常罕见的情况,因此你可能会使用错误的工具来完成这项工作.如果您需要等待某些事情(特别是取消),您可以使用TaskCompletionSource替代品.如果您需要对取消做出反应,可以使用CancellationToken.Register附加回调(因此避免阻塞线程).

  • @ user4388177我们正在阅读同一篇文章吗?我看到`WaitHandle.WaitAny(new [] {wh,token.WaitHandle}); "这是两个手柄 (2认同)