因此,只要应用程序正在运行或要求取消,我的应用程序就需要几乎连续地执行操作(每次运行之间要暂停10秒左右)。它需要做的工作可能要花费30秒。
最好使用System.Timers.Timer并使用AutoReset来确保它在上一个“刻度”完成之前没有执行操作。
还是应该在带有取消令牌的LongRunning模式下使用常规Task,并在其中执行常规的while循环,以调用之间用10秒的Thread.Sleep进行操作?至于异步/等待模型,我不确定这里是否合适,因为我没有任何返回值。
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>在处理完操作并等待适当时间后将项目发布到其自身的对象。
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结构 ; 您必须传递一个类型参数,它也可能传递一些有用的状态(可以根据需要更改状态的性质)。
DateTimeOffset
另外,请注意,ActionBlock<TInput>默认情况下,一次只能处理 一项 ,因此可以确保只处理一项操作(这意味着,当它再次调用扩展方法时,您不必处理重入)。Post
Post
我还将该CancellationToken结构传递给的构造函数ActionBlock<TInput>和Task.Delay方法调用;如果取消了该过程,则取消将在第一个可能的机会发生。
CancellationToken
Task.Delay
从那里,可以轻松地重构代码来存储实现的ITargetBlock<DateTimeoffset>接口ActionBlock<TInput>(这是代表作为使用者的块的高级抽象,并且您希望能够通过调用Post扩展方法来触发使用):
ITargetBlock<DateTimeoffset>
CancellationTokenSource wtoken; ActionBlock<DateTimeOffset> task;
您的StartWork方法:
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方法:
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现在,该方法是一家工厂,可以创建您的“服务”。您可以控制它的启动和停止时间,它是完全独立的。您不必将计时器的状态控制与代码的其他方面交织在一起。您只需创建块,然后启动它,然后在完成时停止它即可。
CreateNeverEndingTask
更有效地使用线程/任务/资源
对于Task线程池,TPL数据流中块的默认调度程序是相同的。通过使用ActionBlock<TInput>来处理您的操作以及对的调用Task.Delay,您可以在实际上不执行任何操作的情况下控制所使用的线程。当然,当您生成新的Task将继续处理的代码时,这实际上会导致一些开销,但是考虑到您不是在紧密的循环中进行处理(每次调用之间要等待十秒钟),所以这应该很小。
如果DoWork实际上可以使该函数处于等待状态(即,该函数返回a Task),那么您可以(可能)通过调整上面的factory方法采用a Func<DateTimeOffset, CancellationToken, Task>而不是来优化此效果Action<DateTimeOffset>,如下所示:
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具有以下签名的方法:
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); }