Task <T>がタイムアウトで完了するのを非同期的に待つ


387

Task <T>がいくつかの特別なルールで完了するのを待ちたい:Xミリ秒後に完了しない場合は、ユーザーにメッセージを表示したい。Yミリ秒が経過しても完了しない場合は、自動的にキャンセルをリクエストします。

Task.ContinueWithを使用して、タスクが完了するまで非同期的に待機できます(つまり、タスクが完了したときにアクションが実行されるようにスケジュールします)が、タイムアウトを指定することはできません。Task.Waitを使用して、タスクがタイムアウトで完了するのを同期的に待機できますが、それによってスレッドがブロックされます。タスクがタイムアウトで完了するのを非同期で待機するにはどうすればよいですか?


3
あなたが正しいです。タイムアウトに対応していないことに驚いています。たぶん.NET 5.0で...もちろん、タイムアウトをタスク自体に組み込むこともできますが、それは良くないことです。
Aliostad 2010年

4
あなたが説明する2層のタイムアウトのロジックは依然として必要ですが、.NET 4.5は確かにタイムアウトベースのを作成するための単純な方法を提供しますCancellationTokenSource。コンストラクタへの2つのオーバーロードが利用可能で、1つは整数ミリ秒の遅延を取り、もう1つはTimeSpan遅延をとります。
パトリッジ

ここに完全な単純なlibソース:stackoverflow.com/questions/11831844/…–

完全なソースコードが機能する最終的な解決策はありますか?おそらく各スレッドの通知エラーのより複雑なサンプルで、WaitAllが概要を表示した後ですか?
Kiquenet 2014年

回答:


565

これはどう:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

そして、この種のことについてのより多くの情報を含む素晴らしいブログ投稿「Crafting a Task.TimeoutAfter Method」(MS Parallel Libraryチームから)があります

追加:私の回答に対するコメントのリクエストに応じて、キャンセル処理を含む拡張ソリューションを以下に示します。キャンセルをタスクとタイマーに渡すことは、コードでキャンセルを経験する方法が複数あることを意味します。テストを行い、それらすべてを適切に処理できることを確認してください。さまざまな組み合わせを行う機会を逃さず、実行時にコンピューターが正しいことを実行することを期待してください。

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
}

86
Task.Delayが長時間実行タスクの前に完了し、タイムアウトのシナリオを処理できる場合でも、これは長時間実行タスク自体をキャンセルしないことに注意してください。WhenAnyは、渡されたタスクの1つが完了したことを通知します。CancellationTokenを実装し、長時間実行されているタスクを自分でキャンセルする必要があります。
ジェフシューマッハ

30
また、Task.Delayタスクはシステムタイマーによってサポートされていることに注意してください。システムタイマーは、時間SomeOperationAsyncがかかるかどうかに関係なく、タイムアウトになるまで追跡され続けます。したがって、この全体的なコードスニペットがタイトなループで頻繁に実行される場合、タイマーがすべてタイムアウトするまで、タイマーのシステムリソースを消費しています。これを修正する方法は、タイマーリソースを解放するために完了したときにキャンセルするCancellationTokenことを渡すTask.Delay(timeout, cancellationToken)ことですSomeOperationAsync
Andrew Arnott 2014年

12
キャンセルコードが実行している作業が多すぎます。これを試してください:int timeout = 1000; var cancelTokenSource = new CancellationTokenSource(timeout); var cancelToken = tokenSource.Token; var task = SomeOperationAsync(cancellationToken); {タスクを待つ; //正常に完了するためのコードをここに追加} catch(OperationCancelledException){//タイムアウトの場合のためにここにコードを追加}
srm

3
@ilansがを待機することTaskにより、タスクによって格納された例外はすべてその時点で再スローされます。これにより、OperationCanceledException(キャンセルされた場合)またはその他の例外(障害が発生した場合)をキャッチする機会が与えられます。
Andrew Arnott

3
@TomexOu:問題は、タスクの完了を非同期で待機する方法でした。Task.Wait(timeout)非同期的に待機するのではなく、同期的にブロックします。
Andrew Arnott

221

Andrew Arnottが彼の回答へのコメントで提案したように、元のタスクが完了したときのタイムアウトのキャンセルを組み込んだ拡張メソッドバージョンを次に示します

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.");
        }
    }
}

8
この男にいくつかの投票をしてください。エレガントなソリューション。また、呼び出しに戻り値の型がない場合は、TResultを削除してください。
Lucas

6
CancellationTokenSourceは使い捨てであり、usingブロック内にある必要があります
PeterM

6
@ It'satrapタスクを2回待機すると、2回目の待機で結果が返されます。2回実行されることはありません。task.Result 2回実行すると等しいと言えます。
M.ミンペン2017年

7
taskタイムアウトが発生しても、元のタスク()は引き続き実行されますか?
jag

6
マイナーな改善の機会:TimeoutException適切なデフォルトメッセージがあります。「操作がタイムアウトしました」で上書きします。値を追加せず、実際にそれをオーバーライドする理由があることを暗示することにより、混乱を引き起こします。
エドワードブレイ2018

49

Task.WaitAny複数のタスクの最初の待機に使用できます。

(指定されたタイムアウト後に完了する)2つの追加タスクを作成してから、を使用WaitAnyして、最初に完了する方を待機することができます。最初に完了したタスクが「作業」タスクである場合は、完了です。最初に完了したタスクがタイムアウトタスクの場合は、タイムアウトに対応できます(リクエストのキャンセルなど)。


1
私が本当に尊敬しているMVPがこのテクニックを使用しているのを見てきましたが、受け入れられた回答よりもずっとクリーンなようです。たぶん、例はもっと多くの票を獲得するのに役立つでしょう!それが役立つと確信するのに十分なタスクの経験がない場合を除いて、私はそれを
自発的に行い

3
1つのスレッドがブロックされますが、問題がなければ問題ありません。スレッドがブロックされていないので、私が取った解決策は以下のものです。私は本当に良かったブログ記事を読みました。
JJschk 2013

@JJschkあなたはあなたが解決策を取ったと言っていますbelow....それはそれですか?SOの注文に基づいていますか?
BozoJoe

遅いタスクをキャンセルしたくない場合はどうすればよいですか?私は...それが完了したときにそれを処理したいが、現在のメソッドからの戻り
Akmal Salikhov

18

このようなものはどうですか?

    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オプションを使用できます。


実際、この例では、t1内ではなく、上のタスクで待機しています。より詳細な例を作ってみます。
as-cii 2010年

14

以下は、トップ投票の回答に基づく完全に機能する例です。

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秒)ハングし、エラーを表示して問題なく続行するか、再試行します。


11

Stephen Clearyの優れたAsyncExライブラリを使用すると、次のことができます。

TimeSpan timeout = TimeSpan.FromSeconds(10);

using (var cts = new CancellationTokenSource(timeout))
{
    await myTask.WaitAsync(cts.Token);
}

TaskCanceledException タイムアウトが発生するとスローされます。


10

これは以前の回答のわずかに強化されたバージョンです。

  • Lawrenceの回答に加えて、タイムアウトが発生すると元のタスクをキャンセルします。
  • addtionで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, timeoutCancellation.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();
        }
    }
}

使用法

InnerCallAsync完了するまでに時間がかかる場合があります。CallAsyncタイムアウトでラップします。

async Task<int> CallAsync(CancellationToken cancellationToken)
{
    var timeout = TimeSpan.FromMinutes(1);
    int result = await CancelAfterAsync(ct => InnerCallAsync(ct), timeout,
        cancellationToken);
    return result;
}

async Task<int> InnerCallAsync(CancellationToken cancellationToken)
{
    return 42;
}

1
解決策をありがとう!あなたが渡す必要がありますように思えるtimeoutCancellationdelayTask。現在、キャンセルを発火すると、の代わりにCancelAfterAsyncスローされる場合がTimeoutExceptionありTaskCanceledException、原因delayTaskが最初に終了する場合があります。
AxelUser

@AxelUser、あなたは正しい。何が起こっているのかを理解するために、たくさんの単体テストで1時間かかりました:)与えられた両方のタスクWhenAnyが同じトークンによってキャンセルされるとWhenAny、最初のタスクが返されると想定しました。その仮定は間違っていました。回答を編集しました。ありがとう!
JosefBláha19年

これを定義済みのTask <SomeResult>関数で実際に呼び出す方法を理解するのに苦労しています。それを呼び出す方法の例に取り組むことができる可能性はありますか?
jhaagsma

1
@jhaagsma、例が追加されました!
JosefBláha

@JosefBláhaありがとうございます!まだラムダスタイルの構文に頭を抱えていますが、それは私には起こりませんでした。ラムダ関数を渡すことで、トークンがCancelAfterAsyncの本体のタスクに渡されます。気の利いた!
jhaagsma

8

タイマーを使用してメッセージと自動キャンセルを処理します。タスクが完了したら、タイマーでDisposeを呼び出して、タイマーが起動しないようにします。以下に例を示します。taskDelayを500、1500、または2500に変更して、さまざまなケースを確認します。

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メソッドが用意されています。これにより、Timerが起動したときに継続するために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;
}

彼は現在のスレッドがブロックされることを望んでいませんtask.Wait()。つまり、いいえです。
Cheng Chen

@Danny:それは例を完全にするためだけのものでした。ContinueWithの後、戻ってタスクを実行できます。わかりやすくするために回答を更新します。
Quartermeister、2010年

2
@dtb:t1をTask <Task <Result >>にしてTaskExtensions.Unwrapを呼び出すとどうなりますか?内側のラムダからt2を返すことができ、その後、ラップされていないタスクに継続を追加できます。
Quartermeister、2010年

驚くばかり!これで問題は完全に解決しました。ありがとう!@ AS-CIIによって提案されたソリューションを使用すると思いますが、TaskExtensionsを提案するためにあなたの答えも受け入れたいと思います。
dtb 2010年

6

この問題を解決する別の方法は、Reactive Extensionsを使用することです。

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;

4

上記の@Kevanの回答の汎用バージョンで、Reactive Extensionsを使用しています。

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 is null 
       ? task.ToObservable().Timeout(timeout).ToTask() 
       : task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

ところで、タイムアウトが発生すると、タイムアウト例外がスローされます


0

BlockingCollectionを使用してタスクをスケジュールすると、プロデューサーは長時間実行される可能性のあるタスクを実行でき、コンシューマーはタイムアウトとキャンセルトークンが組み込まれたTryTakeメソッドを使用できます。


私は何かを書く必要があります(ここにプロプライエタリなコードを入れたくない)が、シナリオはこのようなものです。プロデューサーは、タイムアウトになる可能性のあるメソッドを実行し、完了時に結果をキューに入れるコードになります。コンシューマはタイムアウトを指定してtrytake()を呼び出し、タイムアウト時にトークンを受け取ります。プロデューサーとコンシューマーはどちらもバックラウンドタスクであり、必要に応じてUIスレッドディスパッチャーを使用してユーザーにメッセージを表示します。
kns98

0

私はTask.Delay()タスクを感じましたCancellationTokenSource、そして他のものではタイトなネットワークループでの私のユースケースに対して少し多く答えます。

また、Joe 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;

0

アンドリューアーノットの答えのいくつかの変形:

  1. 既存のタスクを待機して、それが完了したかタイムアウトしたかを確認したいが、タイムアウトが発生した場合にキャンセルしたくない場合:

    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
        }
    }
  2. タイムアウトが発生した場合に作業タスクを開始し、作業をキャンセルする場合:

    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
    }
  3. タイムアウトが発生した場合にキャンセルするタスクがすでに作成されている場合:

    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
    }

別のコメントとして、タイムアウトが発生しない場合、これらのバージョンはタイマーをキャンセルします。そのため、複数の呼び出しによってタイマーが蓄積することはありません。

sjb


0

ここで他のいくつかの回答のアイデアと、別のスレッドでのこの回答を Tryスタイルの拡張メソッドに再結合しています。これは、拡張メソッドが必要で、タイムアウト時の例外を回避する場合に利点があります。

public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task,
    TimeSpan timeout, Action<TResult> successor)
{

    using var timeoutCancellationTokenSource = new CancellationTokenSource();
    var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token))
                                  .ConfigureAwait(continueOnCapturedContext: false);

    if (completedTask == task)
    {
        timeoutCancellationTokenSource.Cancel();

        // propagate exception rather than AggregateException, if calling task.Result.
        var result = await task.ConfigureAwait(continueOnCapturedContext: false);
        successor(result);
        return true;
    }
    else return false;        
}     

async Task Example(Task<string> task)
{
    string result = null;
    if (await task.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), r => result = r))
    {
        Console.WriteLine(result);
    }
}    

-3

絶対にこれを行わないでください。しかし、それは...正当な理由が考えられない場合のオプションです。

((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.