c# – 实现永无止境的任务的正确方法。 (计时器vs任务)

因此,只要应用程序正在运行或请求取消,我的应用程序需要几乎连续执行一个动作(每次运行之间暂停10秒左右)。它需要做的工作有可能需要长达30秒。

最好使用System.Timers.Timer并使用AutoReset来确保它在上一个“tick”完成之前不执行操作。

或者我应该使用LongRunning模式中的一个一般任务与取消令牌,并有一个常规的无限while循环里面它调用的操作做一个工作与10秒Thread.Sleep之间的调用?至于async / await模型,我不确定这里是否适当,因为我没有从工作的任何返回值。

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

或者只是使用一个简单的计时器,同时使用其AutoReset属性,并调用.Stop()取消它?

我会使用TPL Dataflow这个(因为你使用的是.NET 4.5,它在内部使用Task)。你可以很容易地创建一个ActionBlock<TInput>,它在处理它的动作并等待适当的时间后向其自身发布项目。

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

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< TInput>拿一个DateTimeOffset structure;你必须传递一个类型参数,它可能会传递一些有用的状态(你可以改变状态的性质,如果你想要的)。

另外,注意,ActionBlock< TInput>默认情况下一次只处理一个项目,所以你保证只有一个操作将被处理(这意味着,当它调用Post extension method自身时,你不必处理reentrancy)。

我也已经将CancellationToken structure传递给ActionBlock的构造函数< TInput>和Task.Delay method电话;如果该过程被取消,则取消将在第一个可能的机会发生。

从那里,它是一个容易的重构你的代码来存储ITargetBlock<DateTimeoffset> interface实现的ActionBlock< TInput> (这是表示作为消费者的块的更高级抽象,并且您希望能够通过调用Post扩展方法来触发消耗):

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

然后你的StopWork方法:

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 Dataflow?几个原因:

关注点分离

CreateNeverEndingTask方法现在是创建您的“服务”的工厂。你控制什么时候开始和停止,它是完全自给自足。你不必将定时器的状态控制与代码的其他方面交织在一起。您只需创建块,启动它,并在完成后停止它。

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

TPL数据流中的块的默认调度程序对于作为线程池的任务是相同的。通过使用ActionBlock< TInput>以处理您的操作,以及对Task.Delay的调用,当你没有做任何事情,你正在使用的线程的控制。确实,这实际上导致一些开销,当您生成将处理延续的新任务,但这应该是小,考虑到你不是在一个严格的循环(你在调用之间等待10秒)处理这个。

如果DoWork函数实际上可以等待(即,它返回一个任务),那么你可以(可能)通过调整上面的工厂方法来取得一个Func<DateTimeOffset, CancellationToken, Task>,而不是一个Action< DateTimeOffset> :

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到你的方法(如果它接受一个),这是在这里完成。

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

Task DoWorkAsync(CancellationToken cancellationToken);

你必须改变(只是轻微地,你不出血的分离的关注这里)StartWork方法来解释传递给CreateNeverEndingTask方法的新签名,像这样:

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);
}
http://stackoverflow.com/questions/13695499/proper-way-to-implement-a-never-ending-task-timers-vs-task

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:c# – 实现永无止境的任务的正确方法。 (计时器vs任务)