是否有基于任务的替代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适应:
- 向
Task.Delay()
添加ConfigureAwait(false)
以在线程池线程上执行doWork
动作,否则doWork
将在调用线程上执行,这不是并行的想法 - 通过抛出TaskCanceledException(仍然需要?)来坚持取消模式
- 将CancellationToken转发到
doWork
以使其取消任务 - 添加一个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(); } }