[c#] System.Threading.Timer에 대한 작업 기반 대체가 있습니까?
저는 .Net 4.0의 Tasks를 처음 접했고 Task 기반 대체 또는 Timer 구현 (예 : 주기적 작업)을 찾을 수 없었습니다. 그런 것이 있습니까?
업데이트
저는 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가 작업 완료 소스로 타이머 만료를 사용하기 때문에 실제로 다른 제안 된 접근 방식과 유사합니다.
답변
UPDATE
나는 아래의 답변을 마킹 이 된만큼 우리가 비동기 / await를 패턴을 사용하는 것이 이제부터 “대답”으로한다. 더 이상 찬성 할 필요가 없습니다. LOL
Amy가 대답했듯이 Tasked 기반주기 / 타이머 구현은 없습니다. 그러나 내 원래 UPDATE를 기반으로 우리는 이것을 매우 유용하고 생산 테스트를 거친 것으로 발전 시켰습니다. 공유 할 생각 :
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 . . .
답변
정확히는 System.Threading.Tasks
아니지만 Reactive Extensions 라이브러리의 Observable.Timer
(또는 더 간단 함 Observable.Interval
) 아마도 당신이 찾고있는 것일 것입니다.
답변
지금까지 스레딩 타이머 대신주기적인 CPU 바인딩 백그라운드 작업에 LongRunning TPL 작업을 사용했습니다.
- TPL 작업은 취소를 지원합니다.
- 스레딩 타이머는 프로그램이 종료되는 동안 다른 스레드를 시작하여 폐기 된 리소스에 문제를 일으킬 수 있습니다.
- 오버런 가능성 : 스레딩 타이머는 예상치 못한 긴 작업으로 인해 이전 스레드가 계속 처리되는 동안 다른 스레드를 시작할 수 있습니다 (타이머를 중지했다가 다시 시작하면 방지 할 수 있음).
그러나 TPL 솔루션은 항상 다음 작업을 기다리는 동안 필요하지 않은 전용 스레드를 요구합니다 (대부분의 경우). Jeff의 제안 된 솔루션을 사용하여 백그라운드에서 CPU 바운드 순환 작업을 수행하고 싶습니다. 왜냐하면 확장성에 더 좋은 작업이있을 때 (특히 간격 기간이 클 때) 스레드 풀 스레드 만 필요하기 때문입니다.
이를 달성하기 위해 4 가지 적응을 제안합니다.
- 추가
ConfigureAwait(false)
받는Task.Delay()
실행doWork
, 그렇지 않으면, 스레드 풀 스레드에 대한 조치를doWork
병렬 처리 개념이 아닌 호출 스레드에서 수행됩니다. - 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 \ 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);
}
답변
동기 메서드에서 반복되는 비동기 작업을 트리거해야했습니다.
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);
}
}
}
이것은 Jeff의 대답을 수정 한 것입니다. 에 걸릴로 변경 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; }
}
단순한…