16

同期メソッドの実行に時間がかかりすぎる場合にタイムアウト例外をスローする効率的な方法を探しています。いくつかのサンプルを見てきましたが、私が望むものは何もありません。

私がしなければならないことは

  1. 同期方法が SLA を超えていることを確認する
  2. タイムアウト例外をスローする場合

実行時間が長すぎる場合、同期メソッドを終了する必要はありません。(複数の障害が発生すると、回路ブレーカーが作動し、カスケード障害が防止されます)

これまでの私の解決策は以下のとおりです。タイムアウト時にキャンセル要求を受け入れることを期待して、CancellationToken を sync メソッドに渡していることに注意してください。また、私のソリューションは、呼び出し元のコードで必要に応じて待機できるタスクを返します。

私の懸念は、このコードが監視中のメソッドごとに 2 つのタスクを作成することです。TPLはこれをうまく処理すると思いますが、確認したいと思います。

これは理にかなっていますか?これを行うより良い方法はありますか?

private Task TimeoutSyncMethod( Action<CancellationToken> syncAction, TimeSpan timeout )
{
  var cts = new CancellationTokenSource();

  var outer = Task.Run( () =>
  {
     try
     {
        //Start the synchronous method - passing it a cancellation token
        var inner = Task.Run( () => syncAction( cts.Token ), cts.Token );

        if( !inner.Wait( timeout ) )
        {
            //Try give the sync method a chance to abort grecefully
            cts.Cancel();
            //There was a timeout regardless of what the sync method does - so throw
            throw new TimeoutException( "Timeout waiting for method after " + timeout );
        }
     }
     finally
     {
        cts.Dispose();
     }
  }, cts.Token );

  return outer;
}

編集:

@Timothyの答えを使用して、私は今これを使用しています。コードが大幅に減ったわけではありませんが、より明確になりました。ありがとう!

  private Task TimeoutSyncMethod( Action<CancellationToken> syncAction, TimeSpan timeout )
  {
    var cts = new CancellationTokenSource();

    var inner = Task.Run( () => syncAction( cts.Token ), cts.Token );
    var delay = Task.Delay( timeout, cts.Token );

    var timeoutTask = Task.WhenAny( inner, delay ).ContinueWith( t => 
      {
        try
        {
          if( !inner.IsCompleted )
          {
            cts.Cancel();
            throw new TimeoutException( "Timeout waiting for method after " + timeout );
          }
        }
        finally
        {
          cts.Dispose();
        }
      }, cts.Token );

    return timeoutTask;
  }
4

4 に答える 4

23

Task呼び出された がある場合はtask、次のことができます。

var delay = Task.Delay(TimeSpan.FromSeconds(3));
var timeoutTask = Task.WhenAny(task, delay);

になった場合timeoutTask.Resulttask、タイムアウトしませんでした。それ以外の場合は、delayタイムアウトになりました。

これが実装したものと同じように動作するかどうかはわかりませんが、これは組み込みの方法です。

于 2013-09-03T16:53:09.383 に答える
2

Timothy Shields クリーン ソリューションを詳しく説明するには:

        if (task == await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(3))))
        {
            return await task;
        }
        else
            throw new TimeoutException();

私が見つけたこのソリューションは、タスクに戻り値がある場合も処理します-つまり:

async Task<T>

詳細はこちら: MSDN: Crafting a Task.TimeoutAfter Method

于 2015-06-10T11:34:41.727 に答える
2

.NET 4.0いくつかの方法が利用できない場合のために、このソリューションを書き直しましDelayた。このバージョンは、 を返すメソッドを監視していますobject。実装方法Delayは次の.NET 4.0とおりです: How to put a task to sleep (or delay) in C# 4.0?

public class OperationWithTimeout
{
    public Task<object> Execute(Func<CancellationToken, object> operation, TimeSpan timeout)
    {
        var cancellationToken = new CancellationTokenSource();

        // Two tasks are created. 
        // One which starts the requested operation and second which starts Timer. 
        // Timer is set to AutoReset = false so it runs only once after given 'delayTime'. 
        // When this 'delayTime' has elapsed then TaskCompletionSource.TrySetResult() method is executed. 
        // This method attempts to transition the 'delayTask' into the RanToCompletion state.
        Task<object> operationTask = Task<object>.Factory.StartNew(() => operation(cancellationToken.Token), cancellationToken.Token);
        Task delayTask = Delay(timeout.TotalMilliseconds);

        // Then WaitAny() waits for any of the provided task objects to complete execution.
        Task[] tasks = new Task[]{operationTask, delayTask};
        Task.WaitAny(tasks);

        try
        {
            if (!operationTask.IsCompleted)
            {
                // If operation task didn't finish within given timeout call Cancel() on token and throw 'TimeoutException' exception.
                // If Cancel() was called then in the operation itself the property 'IsCancellationRequested' will be equal to 'true'.
                cancellationToken.Cancel();
                throw new TimeoutException("Timeout waiting for method after " + timeout + ". Method was to slow :-)");
            }
        }
        finally
        {
            cancellationToken.Dispose();
        }

        return operationTask;
    }

    public static Task Delay(double delayTime)
    {
        var completionSource = new TaskCompletionSource<bool>();
        Timer timer = new Timer();
        timer.Elapsed += (obj, args) => completionSource.TrySetResult(true);
        timer.Interval = delayTime;
        timer.AutoReset = false;
        timer.Start();
        return completionSource.Task;
    }
}

コンソールアプリでそれを使用する方法。

    public static void Main(string[] args)
    {
        var operationWithTimeout = new OperationWithTimeout();
        TimeSpan timeout = TimeSpan.FromMilliseconds(10000);

        Func<CancellationToken, object> operation = token =>
        {
            Thread.Sleep(9000); // 12000

            if (token.IsCancellationRequested)
            {
                Console.Write("Operation was cancelled.");
                return null;
            }

            return 123456;
        };

        try
        {
            var t = operationWithTimeout.Execute(operation, timeout);
            var result = t.Result;
            Console.WriteLine("Operation returned '" + result + "'");
        }
        catch (TimeoutException tex)
        {
            Console.WriteLine(tex.Message);
        }

        Console.WriteLine("Press enter to exit");
        Console.ReadLine();
    }
于 2014-11-21T13:45:21.833 に答える