Я новичок в задачах .Net 4.0, и мне не удалось найти то, что, как я думал, будет заменой на основе задач или реализацией таймера, например периодической задачей. Что-то подобное существует?
Обновление Я придумал то, что, как мне кажется, является решением моих потребностей, которое заключается в том, чтобы обернуть функциональность «Таймер» внутри Задачи с дочерними Задачами, которые все используют 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); } }
Очевидно, вы можете добавить универсальную версию, которая также принимает аргументы. Это фактически похоже на другие предлагаемые подходы, поскольку под капотом Task.Delay используется истечение таймера в качестве источника завершения задачи.
источник
action()
с повторением!cancelToken.IsCancellationRequested
. Так лучше, правда?action
выполнения», я прав?ОБНОВЛЕНИЕ Я помечаю ответ ниже как «ответ», поскольку он уже достаточно стар, и мы должны использовать шаблон async / await. Больше не нужно голосовать против. лол
Как ответила Эми, периодической реализации / таймера на основе задач не существует. Однако, основываясь на моем первоначальном ОБНОВЛЕНИИ, мы превратили это во что-то весьма полезное и проверенное на производстве. Думал, что поделюсь:
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, i.e. 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 . . .
источник
TaskScheduler.FromCurrentSynchronizationContext()
перед настройкойmainAction
. Затем я передаю получившийся планировщик,MainPeriodicTaskAction
чтобы он создал файлsubTask
with.Это не совсем то
System.Threading.Tasks
, ноObservable.Timer
(или прощеObservable.Interval
) из библиотеки Reactive Extensions, вероятно, то, что вы ищете.источник
До сих пор я использовал задачу LongRunning TPL для циклической фоновой работы с привязкой к ЦП вместо таймера потоковой передачи, потому что:
Однако в решении TPL всегда требуется выделенный поток, в котором нет необходимости в ожидании следующего действия (что бывает в большинстве случаев). Я хотел бы использовать предложенное решение Джеффа для выполнения циклической работы с привязкой к ЦП в фоновом режиме, потому что ему нужен поток потокового пула только тогда, когда есть работа, которую нужно выполнить, что лучше для масштабируемости (особенно, когда период интервала большой).
Для этого я бы предложил 4 варианта:
ConfigureAwait(false)
к,Task.Delay()
чтобы выполнитьdoWork
действие в потоке пула потоков, иначеdoWork
будет выполнено в вызывающем потоке, что не является идеей параллелизмаdoWork
чтобы он мог отменить задачу.Насчет пункта 2 я не уверен, async await по-прежнему требует 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); }
Пожалуйста, оставьте свои комментарии к предлагаемому решению ...
Обновление 2016-8-30
Вышеупомянутое решение вызывается не сразу,
doWork()
а начинается сawait Task.Delay().ConfigureAwait(false)
переключения потока дляdoWork()
. Приведенное ниже решение преодолевает эту проблему, заключая первыйdoWork()
вызов вTask.Run()
и ожидая его.Ниже представлена улучшенная замена async \ await для
Threading.Timer
, выполняющая отменяемую циклическую работу и масштабируемая (по сравнению с решением TPL), поскольку она не занимает никакого потока в ожидании следующего действия.Обратите внимание, что в отличие от таймера, время ожидания (
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); }
источник
ConfigureAwait(false)
будет планировать продолжение метода в пуле потоков, поэтому на самом деле это не решает вторую точку, связанную с таймером потоковой передачи. Я тоже не считаюtaskState
необходимым; Захват лямбда-переменных более гибкий и безопасный для типов.await Task.Delay()
иdoWork()
поэтомуdoWork()
сразу же выполнить во время запуска. Но без какой-либо уловки онdoWork()
будет запускаться в вызывающем потоке в первый раз и блокировать его. Стивен, у вас есть решение этой проблемы?Task.Run
.Мне нужно было запускать повторяющиеся асинхронные задачи из синхронного метода.
public static class PeriodicTask { public static async Task Run( Func<Task> action, TimeSpan period, CancellationToken cancellationToken = default(CancellationToken)) { while (!cancellationToken.IsCancellationRequested) { Stopwatch stopwatch = Stopwatch.StartNew(); if (!cancellationToken.IsCancellationRequested) await action(); stopwatch.Stop(); await Task.Delay(period - stopwatch.Elapsed, cancellationToken); } } }
Это адаптация ответа Джеффа. Он изменен на прием.
Func<Task>
Он также гарантирует, что период - это частота его выполнения, вычитая время выполнения задачи из периода для следующей задержки.class Program { static void Main(string[] args) { PeriodicTask .Run(GetSomething, TimeSpan.FromSeconds(3)) .GetAwaiter() .GetResult(); } static async Task GetSomething() { await Task.Delay(TimeSpan.FromSeconds(1)); Console.WriteLine($"Hi {DateTime.UtcNow}"); } }
источник
Я столкнулся с аналогичной проблемой и написал
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(); } }
источник
static class Helper { public async static Task ExecuteInterval(Action execute, int millisecond, IWorker worker) { while (worker.Worked) { execute(); await Task.Delay(millisecond); } } } interface IWorker { bool Worked { get; } }
Просто...
источник