简体   繁体   English

如何在C#中实现计时器的Task Async?

[英]How to implement Task Async for a timer in C#?

I want a given operation to execute for a certain amount of time. 我希望给定的操作执行一段时间。 When that time has expired, send another execution command. 当该时间到期时,发送另一个执行命令。

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

Rather than have a sleep statement in there that's blocking the rest of the application, how can I write this using an Async/Task/Await in C#? 而不是在那里有一个阻塞应用程序其余部分的sleep语句,我怎样才能在C#中使用Async / Task / Await来编写它?

This issue was answered by Joe Hoag in the Parallel Team's blog in 2011: Crafting a Task.TimeoutAfter Method . 这个问题在2011年并行团队的博客中由Joe Hoag回答: 制作一个Task.TimeoutAfter方法

The solution uses a TaskCompletionSource and includes several optimizations (12% just by avoiding captures), handles cleanup and covers edge cases like calling TimeoutAfter when the target task has already completed, passing invalid timeouts etc. 该解决方案使用TaskCompletionSource并包含多个优化(12%仅通过避免捕获),处理清理并涵盖边缘情况,如在目标任务已完成时调用TimeoutAfter,传递无效超时等。

The beauty of Task.TimeoutAfter is that it is very easy to compose it with other continuations becaused it does only a single thing: notifies you that the timeout has expired. Task.TimeoutAfter的美妙之处在于它很容易与其他延续组合,因为它只做一件事:通知你超时已经过期。 It doesnt' try to cancel your task. 它并没有'尝试取消你的任务。 You get to decide what to do when a TimeoutException is thrown. 您可以决定在抛出TimeoutException时要执行的操作。

A quick implementation using async/await by Stephen Toub is also presented, although edge cases aren't covered as well. 还介绍了使用Stephen Toub的async/await的快速实现,尽管边缘情况也未被涵盖。

The optimized implementation is: 优化的实现是:

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;
}

and Stephen Toub's implementation, without checks for edge cases : 和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();
}

Assuming StartDoingStuff and StopDoingStuff have been created as Async methods returning Task then 假设StartDoingStuff和StopDoingStuff已经创建为异步方法,然后返回Task

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

EDIT: If the original questioner wants an asynchronous method that will cancel after a specific period: assuming the method would not be making any network requests but just be doing some processing in memory and the outcome can be aborted arbitrarily without considering its effects, then use a cancellation token: 编辑:如果原始提问者想要一个在特定时间段后取消的异步方法:假设该方法不会发出任何网络请求而只是在内存中进行一些处理,并且结果可以在不考虑其影响的情况下任意中止,那么使用取消令牌:

    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();
        }
    }

EDIT : I should have mentioned you can catch the resulting OperationCanceledException providing the indication on how the Task ended, avoiding the need to mess around with bools. 编辑 :我应该提到你可以捕获生成的OperationCanceledException,提供有关如何结束任务的指示,避免需要乱搞bool。

Here's how I'd do it, using task cancellation pattern (the option without throwing an exception). 这是我如何使用任务取消模式 (没有抛出异常的选项)。

[EDITED] Updated to use Svick's suggestion to set the timeout via CancellationTokenSource constructor . [EDITED]更新为使用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;
}

Here's how to call it from an async method: 以下是如何从异步方法中调用它:

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

Here's how to call it from a regular method and handle the completion asynchronously: 以下是如何从常规方法调用它并异步处理完成:

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());
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM