正确的方式来实现一个永无止境的任务。 (定时器VS任务) [英] Proper way to implement a never ending task. (Timers vs Task)

查看:676
本文介绍了正确的方式来实现一个永无止境的任务。 (定时器VS任务)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,我的应用程序需要几乎连续执行操作(10秒左右,每次运行之间的停顿),只要应用程序是运行或要求取消。它需要做的工作有责任采取长达30秒的可能性。

So, my app needs to perform an action almost continuously (with a pause of 10 seconds or so between each run) for as long as the app is running or a cancellation is requested. The work it needs to do has the possibility of taking up to 30 seconds.

是更好地使用System.Timers.Timer,并使用自动复位,以确保它不执行previous前的行动嘀已经完成。

Is it better to use a System.Timers.Timer and use AutoReset to make sure it doesn't perform the action before the previous "tick" has completed.

或者我应该使用LongRunning模式的一般任务与取消标记,并有定期无限while里面调用动作做的工作与通话之间10秒的Thread.Sleep循环?而对于异步/等待模式,我不知道,因为我没有在工作的任何返回值将是适当这里。

Or should I use a general Task in LongRunning mode with a cancellation token, and have a regular infinite while loop inside it calling the action doing the work with a 10 second Thread.Sleep between calls? As for the async/await model, I'm not sure it would be appropriate here as I don't have any return values from the work.

CancellationTokenSource wtoken;
Task task;

void StopWork()
{
    wtoken.Cancel();

    try 
    {
        task.Wait();
    } catch(AggregateException) { }
}

void StartWork()
{
    wtoken = new CancellationTokenSource();

    task = Task.Factory.StartNew(() =>
    {
        while (true)
        {
            wtoken.Token.ThrowIfCancellationRequested();
            DoWork();
            Thread.Sleep(10000);
        }
    }, wtoken, TaskCreationOptions.LongRunning);
}

void DoWork()
{
    // Some work that takes up to 30 seconds but isn't returning anything.
}

或只使用一个简单的定时器,而使用其自动复位的财产,并调用.Stop()来取消它?

or just use a simple timer while using its AutoReset property, and call .Stop() to cancel it?

推荐答案

我会使用 TPL数据流了解这(因为你使用.NET 4.5,它使用<一个href=\"http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx\"><$c$c>Task内部)。您可以轻松创建一个<一个href=\"http://msdn.microsoft.com/en-us/library/hh194684.aspx\"><$c$c>ActionBlock<TInput>哪些职位项目本身经过它的处理,它的行动,等待的时间适量。

I'd use TPL Dataflow for this (since you're using .NET 4.5 and it uses Task internally). You can easily create an ActionBlock<TInput> which posts items to itself after it's processed it's action and waited an appropriate amount of time.

首先,创建一个工厂,将创建永无止境的任务:

First, create a factory that will create your never-ending task:

ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
    Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
    // Validate parameters.
    if (action == null) throw new ArgumentNullException("action");

    // Declare the block variable, it needs to be captured.
    ActionBlock<DateTimeOffset> block = null;

    // Create the block, it will call itself, so
    // you need to separate the declaration and
    // the assignment.
    // Async so you can wait easily when the
    // delay comes.
    block = new ActionBlock<DateTimeOffset>(async now => {
        // Perform the action.
        action(now);

        // Wait.
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
            // Doing this here because synchronization context more than
            // likely *doesn't* need to be captured for the continuation
            // here.  As a matter of fact, that would be downright
            // dangerous.
            ConfigureAwait(false);

        // Post the action back to the block.
        block.Post(DateTimeOffset.Now);
    }, new ExecutionDataflowBlockOptions { 
        CancellationToken = cancellationToken
    });

    // Return the block.
    return block;
}

我选择了 ActionBlock&LT; TInput&GT; 取<一个href=\"http://msdn.microsoft.com/en-us/library/system.datetimeoffset.aspx\"><$c$c>DateTimeOffset结构;你必须通过一个类型参数,那还不如通过一些有用的状态(你可以改变国家的性质,如果你想)。

I've chosen the ActionBlock<TInput> to take a DateTimeOffset structure; you have to pass a type parameter, and it might as well pass some useful state (you can change the nature of the state, if you want).

另外,请注意, ActionBlock&LT; TInput&GT; 默认情况下只处理的有一个的在一个时间的项目,所以你保证,只有一个行动将被处理(意思是,你将不必应付重入当它调用的<一个HREF =htt​​p://msdn.microsoft.com/en-us/library/hh194836.aspx> 发表扩展方法的回本身)。

Also, note that the ActionBlock<TInput> by default processes only one item at a time, so you're guaranteed that only one action will be processed (meaning, you won't have to deal with reentrancy when it calls the Post extension method back on itself).

我也通过了<一个href=\"http://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken.aspx\"><$c$c>CancellationToken结构来的两个构造函数的 ActionBlock&LT; TInput&GT; ,并在 Task.Delay 方法调用;如果该过程被取消,取消将在第一个可能的机会。

I've also passed the CancellationToken structure to both the constructor of the ActionBlock<TInput> and to the Task.Delay method call; if the process is cancelled, the cancellation will take place at the first possible opportunity.

从那里,这是你的code的简单重构存储<一个href=\"http://msdn.microsoft.com/en-us/library/hh194833.aspx\"><$c$c>ITargetBlock<DateTimeoffset>接口通过实施 ActionBlock&LT; TInput&GT; (这是更高层次的抽象再presenting块是消费者,并希望能够触发

From there, it's an easy refactoring of your code to store the ITargetBlock<DateTimeoffset> interface implemented by ActionBlock<TInput> (this is the higher-level abstraction representing blocks that are consumers, and you want to be able to trigger the consumption through a call to the Post extension method):

CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;

StartWork 方法:

void StartWork()
{
    // Create the token source.
    wtoken = new CancellationTokenSource();

    // Set the task.
    task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);

    // Start the task.  Post the time.
    task.Post(DateTimeOffset.Now);
}

然后你的限紧方法:

void StopWork()
{
    // CancellationTokenSource implements IDisposable.
    using (wtoken)
    {
        // Cancel.  This will cancel the task.
        wtoken.Cancel();
    }

    // Set everything to null, since the references
    // are on the class level and keeping them around
    // is holding onto invalid state.
    wtoken = null;
    task = null;
}

为什么要在这里使用TPL数据流?有几个原因:

Why would you want to use TPL Dataflow here? A few reasons:

关注点分离

CreateNeverEndingTask 方法现在是一个工厂,创建你的服务可以这么说。您可以控制​​何时启动和停止,它是完全独立的。你不必交织与code其他方面的定时器的状态控制。您只需创建块,启动它,并停止它时就大功告成了。

The CreateNeverEndingTask method is now a factory that creates your "service" so to speak. You control when it starts and stops, and it's completely self-contained. You don't have to interweave state control of the timer with other aspects of your code. You simply create the block, start it, and stop it when you're done.

更有效地利用线程/任务/资源

在TPL数据流块中的缺省调度为工作,这是线程池相同。通过使用 ActionBlock&LT; TInput&GT; 来处理你的行动,以及为 Task.Delay 一个电话,你'再次,当你实际上没有做任何事情,你正在使用的线程产生的控制。当然,这实际上导致了一些开销,当你酿出了新的工作将处理的延续,但要小,考虑到你是不是在紧张处理此环(你等待调用之间的10秒钟)。

The default scheduler for the blocks in TPL data flow is the same for a Task, which is the thread pool. By using the ActionBlock<TInput> to process your action, as well as a call to Task.Delay, you're yielding control of the thread that you were using when you're not actually doing anything. Granted, this actually leads to some overhead when you spawn up the new Task that will process the continuation, but that should be small, considering you aren't processing this in a tight loop (you're waiting ten seconds between invocations).

如果在的DoWork 函数实际上可制成awaitable(即,在它返回一个工作),然后你可以(可能)优化这个更通过调整上述工厂方法采取 FUNC&LT;的DateTimeOffset,的CancellationToken,任务&GT; ,而不是一个动作&LT;的DateTimeOffset&GT; ,就像这样:

If the DoWork function actually can be made awaitable (namely, in that it returns a Task), then you can (possibly) optimize this even more by tweaking the factory method above to take a Func<DateTimeOffset, CancellationToken, Task> instead of an Action<DateTimeOffset>, like so:

ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
    Func<DateTimeOffset, CancellationToken, Task> action, 
    CancellationToken cancellationToken)
{
    // Validate parameters.
    if (action == null) throw new ArgumentNullException("action");

    // Declare the block variable, it needs to be captured.
    ActionBlock<DateTimeOffset> block = null;

    // Create the block, it will call itself, so
    // you need to separate the declaration and
    // the assignment.
    // Async so you can wait easily when the
    // delay comes.
    block = new ActionBlock<DateTimeOffset>(async now => {
        // Perform the action.  Wait on the result.
        await action(now, cancellationToken).
            // Doing this here because synchronization context more than
            // likely *doesn't* need to be captured for the continuation
            // here.  As a matter of fact, that would be downright
            // dangerous.
            ConfigureAwait(false);

        // Wait.
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
            // Same as above.
            ConfigureAwait(false);

        // Post the action back to the block.
        block.Post(DateTimeOffset.Now);
    }, new ExecutionDataflowBlockOptions { 
        CancellationToken = cancellationToken
    });

    // Return the block.
    return block;
}

当然,这将是织好的做法的的CancellationToken 通过对你的方法(如果它接受一个),这是在这里完成的。

Of course, it would be good practice to weave the CancellationToken through to your method (if it accepts one), which is done here.

这意味着,你将有一个 DoWorkAsync 方法具有以下签名:

That means you would then have a DoWorkAsync method with the following signature:

Task DoWorkAsync(CancellationToken cancellationToken);

您不得不更改(仅略,而你不在这里流血了关注点分离)的 StartWork 法核算传递给新的签名 CreateNeverEndingTask 方法,像这样:

You'd have to change (only slightly, and you're not bleeding out separation of concerns here) the StartWork method to account for the new signature passed to the CreateNeverEndingTask method, like so:

void StartWork()
{
    // Create the token source.
    wtoken = new CancellationTokenSource();

    // Set the task.
    task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);

    // Start the task.  Post the time.
    task.Post(DateTimeOffset.Now, wtoken.Token);
}

这篇关于正确的方式来实现一个永无止境的任务。 (定时器VS任务)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆