【问题标题】:Check if CancellationToken has been cancelled检查 CancellationToken 是否已被取消
【发布时间】:2019-10-08 11:34:28
【问题描述】:

我创建了一个小演示项目来帮助我了解如何使用取消令牌。我了解您取消了令牌并检查是否已请求取消,但是有没有办法可以检查取消是否已实现?在下面的示例中,我不想在 DoWork() 完成运行之前再次运行 Work()。

public class Program
{
    public static CancellationTokenSource tokenSource;

    private static void Main(string[] args)
    {
        while (true)
        {
            Work();
        }
    }

    public static async void Work()
    {
        tokenSource = new CancellationTokenSource();
        Console.WriteLine("Press any key to START doing work");
        Console.ReadLine();
        Console.WriteLine("Press any key to STOP doing work");
        DoWork(tokenSource.Token);
        Console.ReadLine();
        Console.WriteLine("Stopping...");
        tokenSource.Cancel();
    }

    public static async void DoWork(CancellationToken cancelToken)
    {
        while (true)
        {
            Console.WriteLine("Working...");
            await Task.Run(() =>
            {
                Thread.Sleep(1500);
            });

            if (cancelToken.IsCancellationRequested)
            {
                Console.WriteLine("Work Cancelled!");

                return;
            }
        }
    }
}

【问题讨论】:

  • 不要使用async void。使用async Task。生成的 Task 可以等待(如果被取消则抛出异常)或根据需要显式检查是否完成。

标签: c# asynchronous cancellationtokensource


【解决方案1】:

您通常不想让您的DoWork 函数async void - 改为async Task。这样您就可以看到它何时完成(或被取消)。

您可能还想使用cancelToken.ThrowIfCancellationRequested()。这会抛出一个OperationCanceledException,你可以抓住它。

public class Program
{
    public static CancellationTokenSource tokenSource;

    private static async Task Main(string[] args)
    {
        while (true)
        {
            await Work();
        }
    }

    public static async Task Work()
    {
        tokenSource = new CancellationTokenSource();
        Console.WriteLine("Press any key to START doing work");
        Console.ReadLine();
        Console.WriteLine("Press any key to STOP doing work");

        var task = DoWork(tokenSource.Token);

        Console.ReadLine();
        Console.WriteLine("Stopping...");
        tokenSource.Cancel();

        try
        {
            await task;
        }
        catch (OperationCanceledException)
        {
            // Task was cancelled
        }
    }

    public static async Task DoWork(CancellationToken cancelToken)
    {
        while (true)
        {
            Console.WriteLine("Working...");
            await Task.Run(() =>
            {
                Thread.Sleep(1500);
            });

            cancelToken.ThrowIfCancellationRequested();
        }
    }
}

此代码依赖于 C# 7 中引入的“async main”。如果您没有这个,您可以将 Main 方法编写为:

private static void Main(string[] args)
{
    while (true)
    {
        Work().Wait();
    }
}

【讨论】:

  • @is_oz 循环结构来自 OP 的问题 - 这不是我的代码。对于IsCancellationRequestedThrowIfCancellationRequested,它与这个玩具示例没有太大区别,但它让调用者区分“操作成功完成”和“操作被取消”之间的区别。由于 OP 正在使用取消,因此这是一个有用的区别。
  • 这里工作正常。是什么让您认为它不会在 await task 等待?
  • task.Wait() 也应该抛出一个异常——但那是一个不会被捕获的AggregateException
  • 那个问题怎么让你觉得await task 不会等待任务?这就是 await 关键字的全部意义。
  • @canton7 206 票的答案意味着等待任务不会等待它完成。然后我又完全复制了你的代码并运行了它,它就像你说的那样工作。我在代码的某个地方犯了一个错误,非常抱歉!再次感谢您的回答
【解决方案2】:

如果您的所有操作都是异步且可取消的,您将充分利用CancellationToken。这样取消令牌将立即生效。您无需等待 Thread.Sleep 或其他阻塞调用完成。

public static async Task DoWork(CancellationToken cancellationToken)
{
    while (true)
    {
        await Console.Out.WriteLineAsync("Working...");
        await Task.Delay(1500, cancellationToken);
    }
}

在此示例中,令牌仅传递给 Task.Delay,因为 WriteLineAsync 在 .NET Framework 上不可取消(它在 .NET Core 上)。

当令牌被取消时,Task.Delay 将产生一个OperationCanceledException

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-24
    • 2016-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多