2014-01-15 60 views
1

繼續我期待下面的代碼打印「已取消」:取消Task.Delay(...)調用不與TaskContinuationOptions.OnlyOnCanceled

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(); 
} 

它打印出「斷陷」,這引起了這些問題:

  1. 爲什麼打印「Faulted」而不是「Canceled」?
  2. 我濫用Task.Delay(...)作爲Thread.Sleep(...)的可取消版本嗎?
  3. 睡眠/延遲的正確方法是什麼,仍然對cts.Cancel()做出響應?
+0

我故意和人爲地不使用async/await來理解TPL的工作原理。 – Wally

回答

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

我在原帖中增加了兩個小代碼註釋,以澄清這個想法,除了調用Task.Delay之外,Proc還有其他工作。 – Wally

+0

你是對的。沒有異步/等待,我的代碼綁定了一個線程池線程。盡我所知,依靠編譯器生成的異步/等待代碼是唯一正確(或至少容易)實現此目的的方法。 – Wally

0

下面是我可以給出的最佳答案。

  1. 它打印「Faulted」,因爲Task.Delay(...)正在創建一個新的Task。當該子任務被取消時,它將作爲AggregateException傳播,而不是OperationCanceledException。

  2. Task.Delay(...)可以被用作一個撤銷的版本Thread.sleep代碼的(...)

  3. 以下修改按預期方式工作:

改性:

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

「它作爲AggregateException向上傳播」是的,但那是因爲你正在使用'Wait()'。如果您使用'await',它會傳播爲'OperationCanceledException'。 – svick

+0

@svick謝謝!我有點在不使用異步/等待的人爲約束下工作。看起來,沒有複製一些編譯器生成的async/await/IAsyncStateMachine代碼,沒有直接的方法。 – Wally