是否有基于任务的替代System.Threading.Timer?

我是.Net 4.0的任务新手,我无法find我认为是基于任务的replace或实现定时器,例如定期任务。 有这样的事吗?

更新我想出了我认为是我的需求的解决scheme,即将“计时器”function包含在任务中,其中子任务全部利用CancellationToken并返回任务以便能够参与进一步的任务步骤。

public static Task StartPeriodicTask(Action action, int intervalInMilliseconds, int delayInMilliseconds, CancellationToken cancelToken) { Action wrapperAction = () => { if (cancelToken.IsCancellationRequested) { return; } action(); }; Action mainAction = () => { TaskCreationOptions attachedToParent = TaskCreationOptions.AttachedToParent; if (cancelToken.IsCancellationRequested) { return; } if (delayInMilliseconds > 0) Thread.Sleep(delayInMilliseconds); while (true) { if (cancelToken.IsCancellationRequested) { break; } Task.Factory.StartNew(wrapperAction, cancelToken, attachedToParent, TaskScheduler.Current); if (cancelToken.IsCancellationRequested || intervalInMilliseconds == Timeout.Infinite) { break; } Thread.Sleep(intervalInMilliseconds); } }; return Task.Factory.StartNew(mainAction, cancelToken); } 

这取决于4.5,但是这个工作。

 public class PeriodicTask { public static async Task Run(Action action, TimeSpan period, CancellationToken cancellationToken) { while(!cancellationToken.IsCancellationRequested) { await Task.Delay(period, cancellationToken); if (!cancellationToken.IsCancellationRequested) action(); } } public static Task Run(Action action, TimeSpan period) { return Run(action, period, CancellationToken.None); } } 

显然你可以添加一个带有参数的通用版本。 这实际上类似于其他build议的方法,因为在引擎下Task.Delay使用定时器过期作为任务完成源。

更新我在下面将答案标记为“答案”,因为现在已经够老了,所以我们应该使用asynchronous/等待模式。 没有必要downvote了。 大声笑


正如艾米所回答的那样,没有基于任务的定期/定时器实现。 然而,基于我原来的更新,我们已经演变成了一些相当有用和生产testing的东西。 以为我会分享:

 using System; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication7 { class Program { static void Main(string[] args) { Task perdiodicTask = PeriodicTaskFactory.Start(() => { Console.WriteLine(DateTime.Now); }, intervalInMilliseconds: 2000, // fire every two seconds... maxIterations: 10); // for a total of 10 iterations... perdiodicTask.ContinueWith(_ => { Console.WriteLine("Finished!"); }).Wait(); } } /// <summary> /// Factory class to create a periodic Task to simulate a <see cref="System.Threading.Timer"/> using <see cref="Task">Tasks.</see> /// </summary> public static class PeriodicTaskFactory { /// <summary> /// Starts the periodic task. /// </summary> /// <param name="action">The action.</param> /// <param name="intervalInMilliseconds">The interval in milliseconds.</param> /// <param name="delayInMilliseconds">The delay in milliseconds, ie how long it waits to kick off the timer.</param> /// <param name="duration">The duration. /// <example>If the duration is set to 10 seconds, the maximum time this task is allowed to run is 10 seconds.</example></param> /// <param name="maxIterations">The max iterations.</param> /// <param name="synchronous">if set to <c>true</c> executes each period in a blocking fashion and each periodic execution of the task /// is included in the total duration of the Task.</param> /// <param name="cancelToken">The cancel token.</param> /// <param name="periodicTaskCreationOptions"><see cref="TaskCreationOptions"/> used to create the task for executing the <see cref="Action"/>.</param> /// <returns>A <see cref="Task"/></returns> /// <remarks> /// Exceptions that occur in the <paramref name="action"/> need to be handled in the action itself. These exceptions will not be /// bubbled up to the periodic task. /// </remarks> public static Task Start(Action action, int intervalInMilliseconds = Timeout.Infinite, int delayInMilliseconds = 0, int duration = Timeout.Infinite, int maxIterations = -1, bool synchronous = false, CancellationToken cancelToken = new CancellationToken(), TaskCreationOptions periodicTaskCreationOptions = TaskCreationOptions.None) { Stopwatch stopWatch = new Stopwatch(); Action wrapperAction = () => { CheckIfCancelled(cancelToken); action(); }; Action mainAction = () => { MainPeriodicTaskAction(intervalInMilliseconds, delayInMilliseconds, duration, maxIterations, cancelToken, stopWatch, synchronous, wrapperAction, periodicTaskCreationOptions); }; return Task.Factory.StartNew(mainAction, cancelToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } /// <summary> /// Mains the periodic task action. /// </summary> /// <param name="intervalInMilliseconds">The interval in milliseconds.</param> /// <param name="delayInMilliseconds">The delay in milliseconds.</param> /// <param name="duration">The duration.</param> /// <param name="maxIterations">The max iterations.</param> /// <param name="cancelToken">The cancel token.</param> /// <param name="stopWatch">The stop watch.</param> /// <param name="synchronous">if set to <c>true</c> executes each period in a blocking fashion and each periodic execution of the task /// is included in the total duration of the Task.</param> /// <param name="wrapperAction">The wrapper action.</param> /// <param name="periodicTaskCreationOptions"><see cref="TaskCreationOptions"/> used to create a sub task for executing the <see cref="Action"/>.</param> private static void MainPeriodicTaskAction(int intervalInMilliseconds, int delayInMilliseconds, int duration, int maxIterations, CancellationToken cancelToken, Stopwatch stopWatch, bool synchronous, Action wrapperAction, TaskCreationOptions periodicTaskCreationOptions) { TaskCreationOptions subTaskCreationOptions = TaskCreationOptions.AttachedToParent | periodicTaskCreationOptions; CheckIfCancelled(cancelToken); if (delayInMilliseconds > 0) { Thread.Sleep(delayInMilliseconds); } if (maxIterations == 0) { return; } int iteration = 0; //////////////////////////////////////////////////////////////////////////// // using a ManualResetEventSlim as it is more efficient in small intervals. // In the case where longer intervals are used, it will automatically use // a standard WaitHandle.... // see http://msdn.microsoft.com/en-us/library/vstudio/5hbefs30(v=vs.100).aspx using (ManualResetEventSlim periodResetEvent = new ManualResetEventSlim(false)) { //////////////////////////////////////////////////////////// // Main periodic logic. Basically loop through this block // executing the action while (true) { CheckIfCancelled(cancelToken); Task subTask = Task.Factory.StartNew(wrapperAction, cancelToken, subTaskCreationOptions, TaskScheduler.Current); if (synchronous) { stopWatch.Start(); try { subTask.Wait(cancelToken); } catch { /* do not let an errant subtask to kill the periodic task...*/ } stopWatch.Stop(); } // use the same Timeout setting as the System.Threading.Timer, infinite timeout will execute only one iteration. if (intervalInMilliseconds == Timeout.Infinite) { break; } iteration++; if (maxIterations > 0 && iteration >= maxIterations) { break; } try { stopWatch.Start(); periodResetEvent.Wait(intervalInMilliseconds, cancelToken); stopWatch.Stop(); } finally { periodResetEvent.Reset(); } CheckIfCancelled(cancelToken); if (duration > 0 && stopWatch.ElapsedMilliseconds >= duration) { break; } } } } /// <summary> /// Checks if cancelled. /// </summary> /// <param name="cancelToken">The cancel token.</param> private static void CheckIfCancelled(CancellationToken cancellationToken) { if (cancellationToken == null) throw new ArgumentNullException("cancellationToken"); cancellationToken.ThrowIfCancellationRequested(); } } } 

输出:

 2/18/2013 4:17:13 PM 2/18/2013 4:17:15 PM 2/18/2013 4:17:17 PM 2/18/2013 4:17:19 PM 2/18/2013 4:17:21 PM 2/18/2013 4:17:23 PM 2/18/2013 4:17:25 PM 2/18/2013 4:17:27 PM 2/18/2013 4:17:29 PM 2/18/2013 4:17:31 PM Finished! Press any key to continue . . . 

它不完全在System.Threading.Tasks ,但是Reactive Extensions库中的Observable.Timer (或者更简单的Observable.Interval )可能就是你正在寻找的东西。

到目前为止,我使用了一个LongRunning TPL任务来完成循环CPU绑定的后台工作,而不是线程计时器,因为:

  • TPL任务支持取消
  • 线程定时器可以在程序closures时启动另一个线程,从而导致可能的处理资源问题
  • 超时的机会:线程计时器可能会启动另一个线程,而以前还在处理由于意外长时间的工作(我知道,它可以通过停止和重新启动计时器来防止)

但是,TPL解决scheme总是要求专用线程,而在等待下一个动作(大部分时间)的时候不需要。 我想使用Jeff提出的解决scheme在后台执行CPU绑定循环工作,因为只有在需要做工作时才需要线程池线程,这对于可伸缩性(特别是间隔时间较长时)更为有利。

为了实现这一点,我build议4适应:

  1. Task.Delay()添加ConfigureAwait(false)以在线程池线程上执行doWork动作,否则doWork将在调用线程上执行,这不是并行的想法
  2. 通过抛出TaskCanceledException(仍然需要?)来坚持取消模式
  3. 将CancellationToken转发到doWork以使其取消任务
  4. 添加一个types为object的参数来提供任务状态信息(如TPL任务)

关于第2点我不确定,asynchronous仍然需要TaskCanceledExecption还是只是最佳实践?

  public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken) { do { await Task.Delay(period, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); doWork(taskState, cancellationToken); } while (true); } 

请给你build议的解决scheme的意见…

更新2016-8-30

上面的解决scheme不会立即调用doWork()而是先await Task.Delay().ConfigureAwait(false)来实现doWork()的线程切换。 下面的解决scheme通过将第一个doWork()调用包装在Task.Run()并等待它来解决这个问题。

以下是对Threading.Timer的改进的asynchronous\等待replace,它执行可取消的循环工作,并且可扩展(与TPL解决scheme相比),因为它在等待下一个操作时不占用任何线程。

请注意,与定时器相反,等待时间( period )是恒定的,而不是周期时间; 循环时间是等待时间和可能变化的doWork()的持续时间之和。

  public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken) { await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false); do { await Task.Delay(period, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); doWork(taskState, cancellationToken); } while (true); } 

我遇到了一个类似的问题,并写了一个TaskTimer类,返回一系列完成定时器的任务: https : //github.com/ikriv/tasktimer/ 。

 using (var timer = new TaskTimer(1000).Start()) { // Call DoStuff() every second foreach (var task in timer) { await task; DoStuff(); } }