8

特定の操作を一定時間実行したい。その時間が経過したら、別の実行コマンドを送信します。

StartDoingStuff();
System.Threading.Thread.Sleep(200);
StopDoingStuff();

アプリケーションの残りの部分をブロックしているスリープ ステートメントをそこに含めるのではなく、C# で Async/Task/Await を使用してこれを記述するにはどうすればよいでしょうか?

4

3 に答える 3

10

この問題は、2011 年の Parallel Team のブログで Joe Hoag によって回答されました: Crafting a Task.TimeoutAfter Method

ソリューションは TaskCompletionSource を使用し、いくつかの最適化 (キャプチャを回避するだけで 12%) を含み、クリーンアップを処理し、ターゲット タスクが既に完了したときに TimeoutAfter を呼び出す、無効なタイムアウトを渡すなどのエッジ ケースをカバーします。

Task.TimeoutAfter の優れた点は、タイムアウトの期限が切れたことを通知するという 1 つのことだけを行うため、他の継続と組み合わせて非常に簡単に作成できることです。タスクをキャンセルしようとはしません。TimeoutException がスローされたときに何をするかを決めることができます。

エッジケースもカバーされていませんが、Stephen Toub による簡単な実装async/awaitも紹介されています。

最適化された実装は次のとおりです。

public static Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
    // Short-circuit #1: infinite timeout or task already completed
    if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
    {
        // Either the task has already completed or timeout will never occur.
        // No proxy necessary.
        return task;
    }

    // tcs.Task will be returned as a proxy to the caller
    TaskCompletionSource<VoidTypeStruct> tcs = 
        new TaskCompletionSource<VoidTypeStruct>();

    // Short-circuit #2: zero timeout
    if (millisecondsTimeout == 0)
    {
        // We've already timed out.
        tcs.SetException(new TimeoutException());
        return tcs.Task;
    }

    // Set up a timer to complete after the specified timeout period
    Timer timer = new Timer(state => 
    {
        // Recover your state information
        var myTcs = (TaskCompletionSource<VoidTypeStruct>)state;

        // Fault our proxy with a TimeoutException
        myTcs.TrySetException(new TimeoutException()); 
    }, tcs, millisecondsTimeout, Timeout.Infinite);

    // Wire up the logic for what happens when source task completes
    task.ContinueWith((antecedent, state) =>
    {
        // Recover our state data
        var tuple = 
            (Tuple<Timer, TaskCompletionSource<VoidTypeStruct>>)state;

        // Cancel the Timer
        tuple.Item1.Dispose();

        // Marshal results to proxy
        MarshalTaskResults(antecedent, tuple.Item2);
    }, 
    Tuple.Create(timer, tcs),
    CancellationToken.None,
    TaskContinuationOptions.ExecuteSynchronously,
    TaskScheduler.Default);

    return tcs.Task;
}

エッジケースのチェックなしのStephen Toubの実装:

public static async Task TimeoutAfter(this Task task, int millisecondsTimeout)
{
    if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout))) 
        await task;
    else
        throw new TimeoutException();
}
于 2013-09-06T09:53:55.717 に答える
3

StartDoingStuff と StopDoingStuff が Task を返す Async メソッドとして作成されていると仮定すると、

await StartDoingStuff();
await Task.Delay(200);
await StopDoingStuff();

編集: 元の質問者が、特定の期間後にキャンセルされる非同期メソッドを必要とする場合: メソッドがネットワーク リクエストを作成するのではなく、メモリ内で何らかの処理を行うだけであり、結果をその影響を考慮せずに任意に中止できると仮定して、次に使用します。キャンセル トークン:

    private async Task Go()
    {
        CancellationTokenSource source = new CancellationTokenSource();
        source.CancelAfter(200);
        await Task.Run(() => DoIt(source.Token));

    }

    private void DoIt(CancellationToken token)
    {
        while (true)
        {
            token.ThrowIfCancellationRequested();
        }
    }

編集:結果の OperationCanceledException をキャッチして、タスクがどのように終了したかを示し、ブール値をいじる必要がないようにすることができると述べたはずです。

于 2013-09-05T22:06:27.207 に答える
2

タスクキャンセルパターン(例外をスローしないオプション)を使用して、これを行う方法を次に示します。

[編集済み] Svick の提案を使用して、CancellationTokenSource コンストラクターを介してタイムアウトを設定するように更新されました。

// return true if the job has been done, false if cancelled
async Task<bool> DoSomethingWithTimeoutAsync(int timeout) 
{
    var tokenSource = new CancellationTokenSource(timeout);
    CancellationToken ct = tokenSource.Token;

    var doSomethingTask = Task<bool>.Factory.StartNew(() =>
    {
        Int64 c = 0; // count cycles

        bool moreToDo = true;
        while (moreToDo)
        {
            if (ct.IsCancellationRequested)
                return false;

            // Do some useful work here: counting
            Debug.WriteLine(c++);
            if (c > 100000)
                moreToDo = false; // done counting 
        }
        return true;
    }, tokenSource.Token);

    return await doSomethingTask;
}

非同期メソッドから呼び出す方法は次のとおりです。

private async void Form1_Load(object sender, EventArgs e)
{
    bool result = await DoSomethingWithTimeoutAsync(3000);
    MessageBox.Show("DoSomethingWithTimeout done:" + result); // false if cancelled
}

通常のメソッドから呼び出して、完了を非同期で処理する方法は次のとおりです。

private void Form1_Load(object sender, EventArgs e)
{
    Task<bool> task = DoSomethingWithTimeoutAsync(3000);
    task.ContinueWith(_ =>
    {
        MessageBox.Show("DoSomethingWithTimeout done:" + task.Result); // false is cancelled
    }, TaskScheduler.FromCurrentSynchronizationContext());
}
于 2013-09-05T23:50:07.757 に答える