キャンセルできるようにする必要がある大規模/長時間実行ワークロードのタスクを使用する場合、タスクが実行するアクションに次のようなテンプレートを使用することがよくあります。
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}
OperationCanceledExceptionはエラーとして記録されるべきではありませんが、タスクがキャンセルされた状態に移行する場合は飲み込まないでください。他の例外は、このメソッドの範囲を超えて処理する必要はありません。
これは常に少し不格好で、Visual StudioはデフォルトでOperationCanceledExceptionのスローで壊れます(このパターンを使用しているため、OperationCanceledExceptionの「ユーザー未処理のブレーク」はオフになっています)。
理想的には、私はこのようなことをしたいと思います:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (Exception ex) exclude (OperationCanceledException)
{
Log.Exception(ex);
throw;
}
}
つまり、キャッチに適用される何らかの除外リストがありますが、現在サポートされていない言語サポートはありません(@ eric-lippert:c#vNext feature :))。
別の方法は、継続によるものです。
public void StartWork()
{
Task.Factory.StartNew(() => DoWork(cancellationSource.Token), cancellationSource.Token)
.ContinueWith(t => Log.Exception(t.Exception.InnerException), TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously);
}
public void DoWork(CancellationToken cancelToken)
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
しかし、私は例外が技術的に単一の内部例外を超える可能性があり、最初の例のように例外をログに記録するときにコンテキストがあまりないので、私はそれがあまり好きではありません)。
これはちょっとしたスタイルの問題だと思いますが、もっと良い提案があるのではないかと思っていますか?
例1に固執する必要がありますか?
イーモン
だから問題は何ですか? catch (OperationCanceledException)
ブロックを捨てて、適切な継続を設定するだけです:
var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
{
var i = 0;
try
{
while (true)
{
Thread.Sleep(1000);
cts.Token.ThrowIfCancellationRequested();
i++;
if (i > 5)
throw new InvalidOperationException();
}
}
catch
{
Console.WriteLine("i = {0}", i);
throw;
}
}, cts.Token);
task.ContinueWith(t =>
Console.WriteLine("{0} with {1}: {2}",
t.Status,
t.Exception.InnerExceptions[0].GetType(),
t.Exception.InnerExceptions[0].Message
),
TaskContinuationOptions.OnlyOnFaulted);
task.ContinueWith(t =>
Console.WriteLine(t.Status),
TaskContinuationOptions.OnlyOnCanceled);
Console.ReadLine();
cts.Cancel();
Console.ReadLine();
TPLはキャンセルと障害を区別します。したがって、キャンセル(タスク本体内でOperationCancelledException
をスローする)は障害ではありません。
要点:しないタスク本体内の例外を再スローせずに処理します。
タスクのキャンセルをエレガントに処理する方法は次のとおりです。
var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
Task.Run( () => {
cts.Token.ThrowIfCancellationRequested();
// do background work
cts.Token.ThrowIfCancellationRequested();
// more work
}, cts.Token ).ContinueWith( task => {
if ( !task.IsCanceled && task.IsFaulted ) // suppress cancel exception
Logger.Log( task.Exception ); // log others
} );
var cts = new CancellationTokenSource( 5000 ); // auto-cancel in 5 sec.
var taskToCancel = Task.Delay( 10000, cts.Token );
// do work
try { await taskToCancel; } // await cancellation
catch ( OperationCanceledException ) {} // suppress cancel exception, re-throw others
C#6.0にはこれに対する解決策があります。 フィルタリング例外
int denom;
try
{
denom = 0;
int x = 5 / denom;
}
// Catch /0 on all days but Saturday
catch (DivideByZeroException xx) when (DateTime.Now.DayOfWeek != DayOfWeek.Saturday)
{
Console.WriteLine(xx);
}
このMSDNブログの投稿 によると、OperationCanceledException
をキャッチする必要があります。
async Task UserSubmitClickAsync(CancellationToken cancellationToken)
{
try
{
await SendResultAsync(cancellationToken);
}
catch (OperationCanceledException) // includes TaskCanceledException
{
MessageBox.Show(“Your submission was canceled.”);
}
}
キャンセル可能なメソッドが他のキャンセル可能な操作の間にある場合、キャンセル時にクリーンアップを実行する必要があります。その場合、上記のcatchブロックを使用できますが、必ず適切に再スローしてください。
async Task SendResultAsync(CancellationToken cancellationToken)
{
try
{
await httpClient.SendAsync(form, cancellationToken);
}
catch (OperationCanceledException)
{
// perform your cleanup
form.Dispose();
// rethrow exception so caller knows you’ve canceled.
// DON’T “throw ex;” because that stomps on
// the Exception.StackTrace property.
throw;
}
}
次のようなことができます:
public void DoWork(CancellationToken cancelToken)
{
try
{
//do work
cancelToken.ThrowIfCancellationRequested();
//more work
}
catch (OperationCanceledException) when (cancelToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
Log.Exception(ex);
throw;
}
}