2015-12-18 14 views
5

次のコードは、キャンセルされるタスクを作成します。 await式(ケース1)はをスローしますが、同期Wait()(ケース2)はSystem.Threading.Tasks.TaskCanceledExceptionSystem.AggregateExceptionにラップ)をスローします。OperationCanceledException VS TaskCanceledExceptionタスクがキャンセルされたとき

using System; 
using System.Threading; 
using System.Threading.Tasks; 

public class Program 
{ 
    public static void Main() 
    { 
     Program.MainAsync().Wait(); 
    } 

    private static async Task MainAsync() 
    { 
     using(var cancellationTokenSource = new CancellationTokenSource()) 
     { 
      var token = cancellationTokenSource.Token; 
      const int cancelationCheckTimeout = 100; 

      var task = Task.Run(
       async() => 
       { 
        for (var i = 0; i < 100; i++) 
        { 
         token.ThrowIfCancellationRequested(); 
         Console.Write("."); 
         await Task.Delay(cancelationCheckTimeout); 
        } 
       }, 
       cancellationTokenSource.Token 
      ); 

      var cancelationDelay = 10 * cancelationCheckTimeout; 
      cancellationTokenSource.CancelAfter(cancelationDelay); 

      try 
      { 
       await task; // (1) 
       //task.Wait(); // (2) 
      } 
      catch(Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
       Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}"); 
       Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}"); 
       Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}"); 
      } 
     } 
    } 
} 

ケース1出力:

..........System.OperationCanceledException: The operation was canceled. 
    at System.Threading.CancellationToken.ThrowIfCancellationRequested() 
    at Program.<>c__DisplayClass1_0.<<MainAsync>b__0>d.MoveNext() 
--- End of stack trace from previous location where exception was thrown --- 
    at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at Program.<MainAsync>d__1.MoveNext() 
Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

ケース2出力:第2ケースに

..........System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    --- End of inner exception stack trace --- 
    at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 
    at System.Threading.Tasks.Task.Wait() 
    at Program.<MainAsync>d__1.MoveNext() 
---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<--- 

Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

なぜSystem.AggregateExceptionは、内部例外としてSystem.OperationCanceledExceptionを含有していませんか?

私はThrowIfCancellationRequested()OperationCanceledExceptionをスローし、我々は両方のケースでTaskはキャンセル(故障していない)状態になっていることを確認することができます知っています。

の.NET APIからメソッドをキャンセルすると、両方のケースで一貫性のある動作を生成するので、これは私のパズル - キャンセルされたタスクは、唯一TaskCanceledExceptionスロー:

using System; 
using System.Threading; 
using System.Threading.Tasks; 

public class Program 
{ 
    public static void Main() 
    { 
     Program.MainAsync().Wait(); 
    } 

    private static async Task MainAsync() 
    { 
     using(var cancellationTokenSource = new CancellationTokenSource()) 
     { 
      var token = cancellationTokenSource.Token; 

      var task = Task.Delay(1000, token); 
      cancellationTokenSource.CancelAfter(100); 

      try 
      { 
       await task; // (1) 
       //task.Wait(); // (2) 
      } 
      catch(Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
       Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}"); 
       Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}"); 
       Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}"); 
      } 
     } 
    } 
} 

ケース1つの出力:

System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at Program.<MainAsync>d__1.MoveNext() 
Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

ケース2の出力を:

System.AggregateException: One or more errors occurred. ---> System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    --- End of inner exception stack trace --- 
    at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) 
    at System.Threading.Tasks.Task.Wait() 
    at Program.<MainAsync>d__1.MoveNext() 
---> (Inner Exception #0) System.Threading.Tasks.TaskCanceledException: A task was canceled.<--- 

Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 
+0

重要ですか? 'TaskCanceledException'は' OperationCanceledException'の派生クラスですので、 'catch(OperationCanceledException e)'を実行した場合、両方のタイプの例外を捕捉できます。あなたが緩める唯一の情報は、 'TaskCanceledException.Task'プロパティです。 –

+0

私は同意しますが、 'OperationCanceledException'が既に存在する場合に' TaskCanceledException'を導入/使用する理由についてもっと興味がありました。 –

+1

これを導入する理由は、タスクを含むより派生したフォームをスローすることができるような状況で取り消されたタスクのコンテキストを取得できるケースがある場合です。'ThrowIfCancellationRequested'は一般的に書かれていて、Taskの内部にあることを知らないので、Taskプロパティなしでより一般的な例外が発生します。 –

答えて

5

ここでの相違点は、token.ThrowIfCancellationRequested()。このメソッドはキャンセルをチェックし、CancellationTokenはTPL専用ではないと理解して、TaskCanceledExceptionではなく、OperationCanceledExceptionを具体的にスローします。あなたはreference sourceを見て、それがこのメソッドを呼び出すことがわかります。

private void ThrowOperationCanceledException() 
{ 
    throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this); 
} 

「正規」のキャンセルしかし確かTaskCanceledExceptionを生成します。あなたはトークンをキャンセルして前にタスクが実行を開始する機会があったことがわかります。

cancellationTokenSource.Cancel(); 
var task = Task.Run(() => { }, cancellationTokenSource.Token); 
try 
{ 
    await task; 
} 
catch (Exception ex) 
{ 
    Console.WriteLine(ex.ToString()); 
    Console.WriteLine($"Task.IsCanceled: {task.IsCanceled}"); 
    Console.WriteLine($"Task.IsFaulted: {task.IsFaulted}"); 
    Console.WriteLine($"Task.Exception: {((task.Exception == null) ? "null" : task.Exception.ToString())}"); 
} 

出力:

System.Threading.Tasks.TaskCanceledException: A task was canceled. 
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
    at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
    at Sandbox.Program.<MainAsync>d__1.MoveNext() 
Task.IsCanceled: True 
Task.IsFaulted: False 
Task.Exception: null 

従来の.NETメソッドは、通常のように非同期APIのためCancellationToken.ThrowIfCancellationRequestedを使用していません作業を別のスレッドにオフロードするときには適切です。これらのメソッドは本質的に非同期操作のため、キャンセルはCancellationToken.Register(または内部のInternalRegisterWithoutEC)を使用して監視されます。

+0

.NETメソッド(例えば 'Task.Delay()'や 'FileStream.WriteAsync()')をキャンセルし、キャンセルされた場合、それらがすべて 'TaskCanceledException'をスローすることに気付きました(あなたが言ったようにTPL専用ではないCancellationToken経由) 。これは、タスク自体を取り消す(それがラップするコールバックではなく)、上の例のように 'TaskCanceledException'をスローすることができます。しかし、なぜ 'FileStream.WriteAsync()'が 'OperationCanceledException'を投げないのですか? 「別のスレッドに推論する」という推論を展開してください。 –

+1

@BojanKomazec 'ThrowIfCancellationRequested'は' OperationCanceledException'をスローします。定期的にキャンセルを確認する必要があるコードを実行しているときにのみ、そのメソッドを呼び出します。ビルトインの非同期APIは同期していないため、これを実行しません。ループ内に何かをするスレッドは保持しません。したがって、 'ThrowIfCancellationRequested'を呼び出すコードはありません。 – i3arnon

+1

例えば、@BojanKomazec 'Task.Delay'は、promiseタスクを作成し、タイムアウトが終了するとタスクを完了するタイマーを開始し、' CancellationToken.Register'でトークンのタスクを取り消すデリゲートを登録します。その後、タスクを返し、それはそれです。 'ThrowIfCancellationRequested'を呼び出すことはどこにもありません。 – i3arnon

関連する問題