이를 위해 TPL Dataflow 를 사용합니다 (.NET 4.5를 사용하고 Task
내부적으로 사용하기 때문에). ActionBlock<TInput>
작업을 처리하고 적절한 시간을 기다린 후 항목을 자신에게 게시하는 항목을 쉽게 만들 수 있습니다 .
먼저 끝없는 작업을 생성 할 공장을 만드십시오.
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action.
action(now);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
나는 구조ActionBlock<TInput>
를 취하기 위해을 선택했다 . 유형 매개 변수를 전달해야하며 유용한 상태를 전달할 수도 있습니다 (원하는 경우 상태의 특성을 변경할 수 있음).DateTimeOffset
또한는 ActionBlock<TInput>
기본적으로 한 번에 하나의 항목 만 처리 하므로 하나의 작업 만 처리됩니다. 즉, 확장 메서드 를 호출 할 때 재진입 을 처리 할 필요가 없습니다.Post
다시 ).
또한 의 생성자 와 메서드 호출 모두에 CancellationToken
구조 를 전달했습니다 . 프로세스가 취소되면 가능한 첫 번째 기회에 취소됩니다.ActionBlock<TInput>
Task.Delay
여기에서 구현 된 ITargetBlock<DateTimeoffset>
인터페이스 를 저장하는 코드를 쉽게 리팩토링 할 수 있습니다 ActionBlock<TInput>
(이것은 소비자 인 블록을 나타내는 상위 수준 추상화이며 Post
확장 메서드 호출을 통해 소비를 트리거 할 수 있기를 원합니다 ).
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
귀하의 StartWork
방법 :
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now);
}
그리고 당신의 StopWork
방법 :
void StopWork()
{
// CancellationTokenSource implements IDisposable.
using (wtoken)
{
// Cancel. This will cancel the task.
wtoken.Cancel();
}
// Set everything to null, since the references
// are on the class level and keeping them around
// is holding onto invalid state.
wtoken = null;
task = null;
}
여기서 TPL Dataflow를 사용하려는 이유는 무엇입니까? 몇 가지 이유 :
우려의 분리
이 CreateNeverEndingTask
방법은 이제 말하자면 "서비스"를 만드는 공장입니다. 시작 및 중지시기를 제어 할 수 있으며 완전히 독립적입니다. 타이머의 상태 제어를 코드의 다른 측면과 섞을 필요가 없습니다. 블록을 만들고 시작하고 완료되면 중지하기 만하면됩니다.
스레드 / 작업 / 리소스를보다 효율적으로 사용
TPL 데이터 흐름의 블록에 대한 기본 스케줄러 Task
는 스레드 풀인 에서 동일 합니다. 를 사용 ActionBlock<TInput>
하여 작업을 처리하고에 대한 호출을 Task.Delay
사용하면 실제로 아무것도하지 않을 때 사용하던 스레드를 제어 할 수 있습니다. 물론, Task
연속을 처리 할 새 항목 을 생성 할 때 실제로 약간의 오버 헤드가 발생 하지만, 타이트한 루프 (호출 사이에 10 초를 기다림)에서 처리하지 않는다는 점을 고려할 때 적어야합니다.
경우 DoWork
실제로 (그것은을 반환에, 즉 awaitable 할 수 기능 Task
), 다음 (아마도) 더 위의 팩토리 메소드를 조정하여이를 최적화 할 수는을하기 Func<DateTimeOffset, CancellationToken, Task>
대신 Action<DateTimeOffset>
, 그래서 같은 :
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
// Validate parameters.
if (action == null) throw new ArgumentNullException("action");
// Declare the block variable, it needs to be captured.
ActionBlock<DateTimeOffset> block = null;
// Create the block, it will call itself, so
// you need to separate the declaration and
// the assignment.
// Async so you can wait easily when the
// delay comes.
block = new ActionBlock<DateTimeOffset>(async now => {
// Perform the action. Wait on the result.
await action(now, cancellationToken).
// Doing this here because synchronization context more than
// likely *doesn't* need to be captured for the continuation
// here. As a matter of fact, that would be downright
// dangerous.
ConfigureAwait(false);
// Wait.
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
// Same as above.
ConfigureAwait(false);
// Post the action back to the block.
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
// Return the block.
return block;
}
물론 CancellationToken
여기에서 수행되는 방법 (만약 허용하는 경우)에 연결하는 것이 좋습니다.
즉 DoWorkAsync
, 다음 서명 이있는 메서드 를 갖게됩니다 .
Task DoWorkAsync(CancellationToken cancellationToken);
StartWork
메서드에 전달 된 새 서명을 설명하는 CreateNeverEndingTask
메서드를 다음과 같이 변경해야합니다 (약간만 여기에서 문제를 분리하지 않습니다) .
void StartWork()
{
// Create the token source.
wtoken = new CancellationTokenSource();
// Set the task.
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
// Start the task. Post the time.
task.Post(DateTimeOffset.Now, wtoken.Token);
}