取消Task.Delay(...)调用不会继续使用TaskContinuationOptions.OnlyOnCanceled

时间:2014-01-15 04:14:58

标签: .net task-parallel-library

我希望以下代码能够打印“已取消”:

static void Main(string[] args)
{
    var cts = new CancellationTokenSource();

    var task = Task.Factory.StartNew(() => Proc(cts.Token), cts.Token);
    task.ContinueWith(t => Console.WriteLine("Completed"), TaskContinuationOptions.OnlyOnRanToCompletion);
    task.ContinueWith(t => Console.WriteLine("Cancelled"), TaskContinuationOptions.OnlyOnCanceled);
    task.ContinueWith(t => Console.WriteLine("Faulted"), TaskContinuationOptions.OnlyOnFaulted);

    Thread.Sleep(1000);
    cts.Cancel();

    Console.ReadKey();
}

static void Proc(CancellationToken token)
{
    // do some CPU-intensive work
    token.ThrowIfCancellationRequested();
    Task.Delay(2000, token).Wait();
    // do some CPU-intensive work
    token.ThrowIfCancellationRequested();
}

它打印出“Faulted”,提出了这些问题:

  1. 为什么打印“Faulted”而不是“Cancelled”?
  2. 我是否误用Task.Delay(...)作为Thread.Sleep(...)的可取消版本?
  3. 睡眠/延迟的正确方法是什么,仍然响应cts.Cancel()?

2 个答案:

答案 0 :(得分:2)

您确实使用此代码滥用Task.Delay()作为Thread.Sleep()的可取消版本:

Task.Delay(2000, token).Wait();

它会阻止您使用Task.Factory.StartNew开始的池线程,以模拟Thread.Sleep()。否则,该线程可以为其他任务做一些有用的工作。

您的Main可以简单地重写为:

static void Main(string[] args)
{
    var cts = new CancellationTokenSource();

    var task = Task.Delay(2000, cts.Token);
    task.ContinueWith(t => Console.WriteLine("Completed"), TaskContinuationOptions.OnlyOnRanToCompletion);
    task.ContinueWith(t => Console.WriteLine("Cancelled"), TaskContinuationOptions.OnlyOnCanceled);
    task.ContinueWith(t => Console.WriteLine("Faulted"), TaskContinuationOptions.OnlyOnFaulted);

    Thread.Sleep(1000);
    cts.Cancel();

    Console.ReadKey();
} 

如果您确实需要一个单独的线程用于某些CPU绑定工作,您仍然可以在其中使用Task.Delay而不会阻塞该线程。循环将在await Task.Delay(...)之后在另一个池线程上继续。例如:

static void Main(string[] args)
{
    var cts = new CancellationTokenSource();
    var token = cts.Token;

    var task = Task.Run(async () => 
    {
        var i = 0;
        while (true)
        {
            token.ThrowIfCancellationRequested();
            Console.WriteLine(CalcPrimeNumber(i++));
            await Task.Delay(200, token); 
        }
    }, token);

    task.ContinueWith(t => Console.WriteLine("Completed"), TaskContinuationOptions.OnlyOnRanToCompletion);
    task.ContinueWith(t => Console.WriteLine("Cancelled"), TaskContinuationOptions.OnlyOnCanceled);
    task.ContinueWith(t => Console.WriteLine("Faulted"), TaskContinuationOptions.OnlyOnFaulted);

    Thread.Sleep(1000);
    cts.Cancel();

    Console.ReadKey();
} 

使用TPL和async/await,除了测试之外,您根本不需要使用Thread.Sleep

答案 1 :(得分:0)

以下是我能给出的最佳答案。

  1. 它打印“Faulted”,因为Task.Delay(...)正在创建一个新的Task。当该子任务被取消时,它会以AggregateException而不是OperationCanceledException传播。

  2. Task.Delay(...)可以用作Thread.Sleep(...)的可取消版本

  3. 以下修改按预期工作:

  4. 修饰:

    static void Proc(CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        try
        {
            Task.Delay(2000, token).Wait();
        }
        catch
        {
            token.ThrowIfCancellationRequested();
            throw;
        }
        token.ThrowIfCancellationRequested();
    }