39

キャンセルできる必要がある大規模/長時間実行のワークロードにタスクを使用する場合、タスクが実行するアクションに次のようなテンプレートを使用することがよくあります。

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

更新:それは2021年であり、C#9は私がいつも望んでいた構文を私に与えます:

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (Exception ex) when (ex is not OperationCanceledException)
    {
        Log.Exception(ex);
        throw;
    }
}
理想的には、次のようなことができるようになりたいと思います。
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に固執する必要がありますか?

4

6 に答える 6

18

だから問題は何ですか?ブロックを捨てて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タスク本体内でのスロー)は障害ではありません

重要なポイント:タスク本体内で例外を再スローせずに処理しないでください。

于 2012-09-28T05:39:19.627 に答える
13

タスクのキャンセルをエレガントに処理する方法は次のとおりです。

「ファイアアンドフォーゲット」タスクの処理

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
于 2016-05-04T21:53:58.577 に答える
10

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);
}
于 2015-12-02T03:50:28.143 に答える
10

あなたはこのようなことをすることができます:

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;
    }
}
于 2019-06-11T13:23:20.217 に答える
0

このMSDNブログ投稿によるとOperationCanceledException、たとえば、キャッチする必要があります

async Task UserSubmitClickAsync(CancellationToken cancellationToken)
{
   try
   {
      await SendResultAsync(cancellationToken);
   }
   catch (OperationCanceledException) // includes TaskCanceledException
   {
      MessageBox.Show(“Your submission was canceled.”);
   }
}

キャンセル可能なメソッドが他のキャンセル可能な操作の間にある場合、キャンセル時にクリーンアップを実行する必要がある場合があります。その際、上記のキャッチブロックを使用できますが、必ず適切に再スローしてください。

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; 
   }
}
于 2017-10-08T02:41:06.240 に答える
-3

ここで何を達成しようとしているのか完全にはわかりませんが、次のパターンが役立つと思います

public void DoWork(CancellationToken cancelToken)
{
    try
    {
        //do work
        cancelToken.ThrowIfCancellationRequested();
        //more work
    }
    catch (OperationCanceledException) {}
    catch (Exception ex)
    {
        Log.Exception(ex);
    }
}

ここからthrowステートメントを削除したことにお気づきかもしれません。これは例外をスローしませんが、単にそれを無視します。

あなたが何か他のことをするつもりなら私に知らせてください。

コードで示したものに非常に近い別の方法があります

    catch (Exception ex)
    {
        if (!ex.GetType().Equals(<Type of Exception you don't want to raise>)
        {
            Log.Exception(ex);

        }
    }
于 2012-09-28T05:39:09.390 に答える