Task<T> がタイムアウトで完了するまで非同期的に待機します。
-
26-09-2019 - |
質問
を待ちたいのですが、 タスク<T> いくつかの特別なルールを追加して完了します。X ミリ秒経過しても完了しない場合は、ユーザーにメッセージを表示します。Y ミリ秒経過しても完了しない場合は、自動的に キャンセルをリクエストする.
使うことができます Task.ContinueWith タスクが完了するまで非同期的に待機します(つまり、タスクの完了時にアクションが実行されるようにスケジュールを設定します)が、タイムアウトを指定することはできません。使うことができます タスク.待機 タイムアウトでタスクが完了するのを同期的に待機しますが、スレッドがブロックされます。タイムアウトになってもタスクが完了するのを非同期に待つにはどうすればよいですか?
解決
これはどうます:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
そして、ここでの偉大なブログ記事「タスクの工芸.TimeoutAfter方法」(MSからライブラリチームパラレル)事に、この種の詳細とます。
の追加の:私の答えにコメントの要請で、ここではキャンセル扱いを含み、拡張ソリューションです。タスクとタイマー手段に取り消しを渡すと、キャンセルがあなたのコードで経験することができ、あなたがのためのテストに必ず、あなたが適切にそれらのすべてを扱う確信する必要があり、複数の方法があることに注意してください。チャンス様々な組み合わせに残して、コンピュータが実行時に正しいことをして期待しないでください。
int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
// Task completed within timeout.
// Consider that the task may have faulted or been canceled.
// We re-await the task so that any exceptions/cancellation is rethrown.
await task;
}
else
{
// timeout/cancellation logic
}
他のヒント
ここでは彼の答えをhref="https://stackoverflow.com/a/11191070/1512">時に元のタスクが完了に。
public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) {
using (var timeoutCancellationTokenSource = new CancellationTokenSource()) {
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
if (completedTask == task) {
timeoutCancellationTokenSource.Cancel();
return await task; // Very important in order to propagate exceptions
} else {
throw new TimeoutException("The operation has timed out.");
}
}
}
あなたが複数のタスクの最初のを待つようにTask.WaitAny
を使用することができます。
あなたはその後、(指定されたタイムアウト後に完了した)とは、最初に完了どちらかを待つWaitAny
を使用する2つの追加のタスクを作成することができます。完了したタスクは、最初にあなたの「仕事」タスクであれば、あなたは完了です。最初に完了したタスクはタイムアウトタスクである場合は、タイムアウト(例えば、要求取消)に反応することができます。
このようなことについては何?
const int x = 3000;
const int y = 1000;
static void Main(string[] args)
{
// Your scheduler
TaskScheduler scheduler = TaskScheduler.Default;
Task nonblockingTask = new Task(() =>
{
CancellationTokenSource source = new CancellationTokenSource();
Task t1 = new Task(() =>
{
while (true)
{
// Do something
if (source.IsCancellationRequested)
break;
}
}, source.Token);
t1.Start(scheduler);
// Wait for task 1
bool firstTimeout = t1.Wait(x);
if (!firstTimeout)
{
// If it hasn't finished at first timeout display message
Console.WriteLine("Message to user: the operation hasn't completed yet.");
bool secondTimeout = t1.Wait(y);
if (!secondTimeout)
{
source.Cancel();
Console.WriteLine("Operation stopped!");
}
}
});
nonblockingTask.Start();
Console.WriteLine("Do whatever you want...");
Console.ReadLine();
}
あなたが別のタスクを使用して、メインスレッドをブロックせずにTask.Waitオプションを使用することができます。
以下は、投票で最も多かった回答に基づいた、完全に機能する例です。
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
この回答の実装の主な利点は、ジェネリックスが追加されているため、関数 (またはタスク) が値を返すことができることです。これは、既存の関数をタイムアウト関数でラップできることを意味します。例:
前に:
int x = MyFunc();
後:
// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
このコードには .NET 4.5 が必要です。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TaskTimeout
{
public static class Program
{
/// <summary>
/// Demo of how to wrap any function in a timeout.
/// </summary>
private static void Main(string[] args)
{
// Version without timeout.
int a = MyFunc();
Console.Write("Result: {0}\n", a);
// Version with timeout.
int b = TimeoutAfter(() => { return MyFunc(); },TimeSpan.FromSeconds(1));
Console.Write("Result: {0}\n", b);
// Version with timeout (short version that uses method groups).
int c = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
Console.Write("Result: {0}\n", c);
// Version that lets you see what happens when a timeout occurs.
try
{
int d = TimeoutAfter(
() =>
{
Thread.Sleep(TimeSpan.FromSeconds(123));
return 42;
},
TimeSpan.FromSeconds(1));
Console.Write("Result: {0}\n", d);
}
catch (TimeoutException e)
{
Console.Write("Exception: {0}\n", e.Message);
}
// Version that works on tasks.
var task = Task.Run(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(1));
return 42;
});
// To use async/await, add "await" and remove "GetAwaiter().GetResult()".
var result = task.TimeoutAfterAsync(TimeSpan.FromSeconds(2)).
GetAwaiter().GetResult();
Console.Write("Result: {0}\n", result);
Console.Write("[any key to exit]");
Console.ReadKey();
}
public static int MyFunc()
{
return 42;
}
public static TResult TimeoutAfter<TResult>(
this Func<TResult> func, TimeSpan timeout)
{
var task = Task.Run(func);
return TimeoutAfterAsync(task, timeout).GetAwaiter().GetResult();
}
private static async Task<TResult> TimeoutAfterAsync<TResult>(
this Task<TResult> task, TimeSpan timeout)
{
var result = await Task.WhenAny(task, Task.Delay(timeout));
if (result == task)
{
// Task completed within timeout.
return task.GetAwaiter().GetResult();
}
else
{
// Task timed out.
throw new TimeoutException();
}
}
}
}
注意事項
この答えを出した後、一般的には ない 絶対に必要な場合を除き、通常の操作中にコード内で例外をスローすることをお勧めします。
- 例外がスローされるたびに、非常に重い操作が発生します。
- 例外が緊密なループ内にある場合、例外によってコードの速度が 100 倍以上遅くなる可能性があります。
このコードは、呼び出している関数を絶対に変更できないため、特定の関数の後にタイムアウトになる場合にのみ使用してください。 TimeSpan
.
この答えは実際には、タイムアウト パラメーターを含めるようにリファクタリングできないサードパーティのライブラリを扱う場合にのみ適用されます。
堅牢なコードの書き方
堅牢なコードを作成したい場合の一般的なルールは次のとおりです。
無期限にブロックされる可能性があるすべての操作にはタイムアウトが必要です。
もし、あんたが しないでください このルールに従うと、コードは最終的に何らかの理由で失敗する操作に遭遇し、その後無期限にブロックされ、アプリは永久にハングすることになります。
しばらくしてから適切なタイムアウトが発生した場合、アプリは非常に長い時間ハングします (例:30 秒)、エラーが表示されてそのまま続行されるか、再試行されます。
を使用するA タイマーのメッセージを処理すると、自動キャンセル。タスクの完了は、そう、彼らは火決してそのタイマー上のDisposeを呼び出すと。ここでは例があります。別の例を見るために500、1500、または2500年にtaskDelayを変更します。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
private static Task CreateTaskWithTimeout(
int xDelay, int yDelay, int taskDelay)
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var task = Task.Factory.StartNew(() =>
{
// Do some work, but fail if cancellation was requested
token.WaitHandle.WaitOne(taskDelay);
token.ThrowIfCancellationRequested();
Console.WriteLine("Task complete");
});
var messageTimer = new Timer(state =>
{
// Display message at first timeout
Console.WriteLine("X milliseconds elapsed");
}, null, xDelay, -1);
var cancelTimer = new Timer(state =>
{
// Display message and cancel task at second timeout
Console.WriteLine("Y milliseconds elapsed");
cts.Cancel();
}
, null, yDelay, -1);
task.ContinueWith(t =>
{
// Dispose the timers when the task completes
// This will prevent the message from being displayed
// if the task completes before the timeout
messageTimer.Dispose();
cancelTimer.Dispose();
});
return task;
}
static void Main(string[] args)
{
var task = CreateTaskWithTimeout(1000, 2000, 2500);
// The task has been started and will display a message after
// one timeout and then cancel itself after the second
// You can add continuations to the task
// or wait for the result as needed
try
{
task.Wait();
Console.WriteLine("Done waiting for task");
}
catch (AggregateException ex)
{
Console.WriteLine("Error waiting for task:");
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine(e);
}
}
}
}
}
<時間>
また、非同期CTP のことTaskEx.Delay方法を提供あなたのための作業にタイマーをラップします。これは、あなたの継続時にタイマーが起動用に設定TaskSchedulerのようなものを行うにはより多くの制御を与えることができます。
private static Task CreateTaskWithTimeout(
int xDelay, int yDelay, int taskDelay)
{
var cts = new CancellationTokenSource();
var token = cts.Token;
var task = Task.Factory.StartNew(() =>
{
// Do some work, but fail if cancellation was requested
token.WaitHandle.WaitOne(taskDelay);
token.ThrowIfCancellationRequested();
Console.WriteLine("Task complete");
});
var timerCts = new CancellationTokenSource();
var messageTask = TaskEx.Delay(xDelay, timerCts.Token);
messageTask.ContinueWith(t =>
{
// Display message at first timeout
Console.WriteLine("X milliseconds elapsed");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
var cancelTask = TaskEx.Delay(yDelay, timerCts.Token);
cancelTask.ContinueWith(t =>
{
// Display message and cancel task at second timeout
Console.WriteLine("Y milliseconds elapsed");
cts.Cancel();
}, TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(t =>
{
timerCts.Cancel();
});
return task;
}
ステファン・クリアリーの優れたを使用して AsyncEx のライブラリ、あなたが行うことができます:
TimeSpan timeout = TimeSpan.FromSeconds(10);
using (var cts = new CancellationTokenSource(timeout))
{
await myTask.WaitAsync(cts.Token);
}
TaskCanceledException
タイムアウトが発生した場合にスローされます。
この問題を解決する別の方法は、反応性の拡張機能を使用しています:
public static Task TimeoutAfter(this Task task, TimeSpan timeout, IScheduler scheduler)
{
return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}
あなたのユニットテストでコードの下に使用して、上記の試験までは、それが
私の作品TestScheduler scheduler = new TestScheduler();
Task task = Task.Run(() =>
{
int i = 0;
while (i < 5)
{
Console.WriteLine(i);
i++;
Thread.Sleep(1000);
}
})
.TimeoutAfter(TimeSpan.FromSeconds(5), scheduler)
.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);
scheduler.AdvanceBy(TimeSpan.FromSeconds(6).Ticks);
あなたは次の名前空間が必要な場合があります
using System.Threading.Tasks;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using Microsoft.Reactive.Testing;
using System.Threading;
using System.Reactive.Concurrency;
反応Extensionsで上記@ Kevanの答えのジェネリックバージョンます。
public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, IScheduler scheduler)
{
return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}
オプションのスケジューラでは、
public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, Scheduler scheduler = null)
{
return scheduler == null
? task.ToObservable().Timeout(timeout).ToTask()
: task.ToObservable().Timeout(timeout, scheduler).ToTask();
}
ところで:タイムアウトが発生した場合、タイムアウト例外がスローされます。
、プロデューサーは、潜在的に長い実行中のタスクを実行することができますし、消費者がタイムアウトとキャンセルトークンに組み込まれます。
はTryTakeメソッドを使用することができますこれは、以前の回答をわずかに強化したバージョンです。
- に加えて ロレンスの答え, 、タイムアウトが発生すると、元のタスクがキャンセルされます。
- それに加えて sjbの回答バリアント2および3, 、提供できます
CancellationToken
元のタスクの場合、タイムアウトが発生すると、TimeoutException
の代わりにOperationCanceledException
.
async Task<TResult> CancelAfterAsync<TResult>(Func<CancellationToken, Task<TResult>> startTask, TimeSpan timeout, CancellationToken cancellationToken)
{
using (var timeoutCancellation = new CancellationTokenSource())
using (var combinedCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token))
{
var originalTask = startTask(combinedCancellation.Token);
var delayTask = Task.Delay(timeout, combinedCancellation.Token);
var completedTask = await Task.WhenAny(originalTask, delayTask);
// Cancel timeout to stop either task:
// - Either the original task completed, so we need to cancel the delay task.
// - Or the timeout expired, so we need to cancel the original task.
// Canceling will not affect a task, that is already completed.
timeoutCancellation.Cancel();
if (completedTask == originalTask)
{
// original task completed
return await originalTask;
}
else
{
// timeout
throw new TimeoutException();
}
}
}
Andrew Arnott の答えのいくつかの変形:
既存のタスクを待機してタスクが完了したかタイムアウトしたかを確認したいが、タイムアウトが発生してもタスクをキャンセルしたくない場合は、次のようにします。
public static async Task<bool> TimedOutAsync(this Task task, int timeoutMilliseconds) { if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); } if (timeoutMilliseconds == 0) { return !task.IsCompleted; // timed out if not completed } var cts = new CancellationTokenSource(); if (await Task.WhenAny( task, Task.Delay(timeoutMilliseconds, cts.Token)) == task) { cts.Cancel(); // task completed, get rid of timer await task; // test for exceptions or task cancellation return false; // did not timeout } else { return true; // did timeout } }
作業タスクを開始し、タイムアウトが発生した場合に作業をキャンセルする場合は、次のようにします。
public static async Task<T> CancelAfterAsync<T>( this Func<CancellationToken,Task<T>> actionAsync, int timeoutMilliseconds) { if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); } var taskCts = new CancellationTokenSource(); var timerCts = new CancellationTokenSource(); Task<T> task = actionAsync(taskCts.Token); if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) { timerCts.Cancel(); // task completed, get rid of timer } else { taskCts.Cancel(); // timer completed, get rid of task } return await task; // test for exceptions or task cancellation }
タイムアウトが発生した場合にキャンセルしたいタスクがすでに作成されている場合:
public static async Task<T> CancelAfterAsync<T>(this Task<T> task, int timeoutMilliseconds, CancellationTokenSource taskCts) { if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); } var timerCts = new CancellationTokenSource(); if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) { timerCts.Cancel(); // task completed, get rid of timer } else { taskCts.Cancel(); // timer completed, get rid of task } return await task; // test for exceptions or task cancellation }
もう 1 つのコメントとして、これらのバージョンでは、タイムアウトが発生しない場合はタイマーがキャンセルされるため、複数の呼び出しによってタイマーが積み重なることはありません。
sjb
私はタイトっぽいネットワーキング・ループの私のユースケースのために少しくらい他の回答にTask.Delay()
タスクとCancellationTokenSource
を感じます。
とジョーHoagだがタイムアウトがないよりも頻繁に期待されているので、感激してのMSDNのブログにTask.TimeoutAfter方法を作り上げる、私は、上記と同じ理由のためのフロー制御のためのTimeoutException
を使用して少し疲れました。
だから私はまたブログで言及した最適化を処理する、本と一緒に行きました。
public static async Task<bool> BeforeTimeout(this Task task, int millisecondsTimeout)
{
if (task.IsCompleted) return true;
if (millisecondsTimeout == 0) return false;
if (millisecondsTimeout == Timeout.Infinite)
{
await Task.WhenAll(task);
return true;
}
var tcs = new TaskCompletionSource<object>();
using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs,
millisecondsTimeout, Timeout.Infinite))
{
return await Task.WhenAny(task, tcs.Task) == task;
}
}
使用事例は、通りである:
var receivingTask = conn.ReceiveAsync(ct);
while (!await receivingTask.BeforeTimeout(keepAliveMilliseconds))
{
// Send keep-alive
}
// Read and do something with data
var data = await receivingTask;