Есть ли замена на основе задачи для System.Threading.Timer?
Я новичок в .Net 4.0 Tasks, и я не смог найти то, что, как я думал, будет заменой или выполнением Таймера на основе задачи, например. периодическая задача. Есть ли такая вещь?
Обновление
Я придумал то, что, по моему мнению, является решением моих потребностей, которое заключается в том, чтобы обернуть функциональность "Таймер" внутри Задачи с дочерними задачами, используя все преимущества 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);
}
Ответы
Ответ 1
Это зависит от 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 использует истечение таймера в качестве источника завершения задачи.
Ответ 2
UPDATE
Я отмечаю ответ ниже как "ответ", так как это достаточно устарело, теперь мы должны использовать шаблон async/await. Не нужно больше ниспровергать это. LOL
Как ответила Эми, не существует периодической реализации таймера или таймера. Однако, основываясь на моем первоначальном ОБНОВЛЕНОМ, мы превратили это во что-то весьма полезное и производственное тестирование. Думаю, я бы разделил:
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 . . .
Ответ 3
Это не совсем в System.Threading.Tasks
, но Observable.Timer
(или более простой Observable.Interval
) из библиотеки Reactive Extensions, вероятно, вы ищете.
Ответ 4
До сих пор я использовал задачу LongRunning TPL для циклической работы с привязкой к центральному процессору вместо таймера потоковой передачи, потому что:
- задача TPL поддерживает отмену
- таймер потоковой передачи может запускать другой поток, пока программа закрывается, вызывая возможные проблемы с удаленными ресурсами.
- вероятность переполнения: таймер потоковой передачи может запустить другой поток, в то время как предыдущий все еще обрабатывается из-за неожиданной продолжительной работы (я знаю, его можно предотвратить, остановив и перезапустив таймер)
Однако решение TPL всегда требует выделенного потока, который не нужен, ожидая следующего действия (которое больше всего времени). Я хотел бы использовать предлагаемое решение Джеффа для выполнения циклической работы с ЦП в фоновом режиме, потому что ему нужен поток потока thread, когда есть работа, которая лучше для масштабируемости (особенно когда период интервала большой).
Чтобы достичь этого, я бы предложил 4 адаптации:
- Добавьте
ConfigureAwait(false)
в Task.Delay()
, чтобы выполнить действие doWork
в потоке пула потоков, иначе doWork
будет выполняться в вызывающем потоке, который не является идеей parallelism
- Придерживайтесь шаблона отмены, бросая исключение TaskCanceledException (все еще требуется?)
- Переслать CancellationToken на
doWork
, чтобы он мог отменить задачу.
- Добавить параметр объекта типа для предоставления информации состояния задачи (например, задачи TPL)
О пункте 2 Я не уверен, что асинхронное ожидание по-прежнему требует 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\ожидания для 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);
}
Ответ 5
Я столкнулся с аналогичной проблемой и написал класс 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();
}
}
Ответ 6
Мне нужно было запускать повторяющиеся асинхронные задачи из синхронного метода.
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}");
}
}
Ответ 7
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; }
}
Просто...