Tôi muốn sử dụng TPL Dataflow cho việc này (vì bạn đang sử dụng .NET 4.5 và nó sử dụng Task
nội bộ). Bạn có thể dễ dàng tạo một ActionBlock<TInput>
mục đăng các mục lên chính nó sau khi nó được xử lý, hành động của nó và đợi một khoảng thời gian thích hợp.
Đầu tiên, hãy tạo một nhà máy sẽ tạo ra nhiệm vụ không bao giờ kết thúc của bạn:
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;
}
Tôi đã chọn ActionBlock<TInput>
lấy một DateTimeOffset
cấu trúc ; bạn phải truyền một tham số kiểu và nó cũng có thể truyền một số trạng thái hữu ích (bạn có thể thay đổi bản chất của trạng thái, nếu bạn muốn).
Ngoài ra, hãy lưu ý rằng ActionBlock<TInput>
theo mặc định chỉ xử lý một mục tại một thời điểm, vì vậy bạn được đảm bảo rằng chỉ một hành động sẽ được xử lý (nghĩa là, bạn sẽ không phải đối phó với lần truy cập lại khi nó gọi lại Post
phương thức tiện ích mở rộng ).
Tôi cũng đã chuyển CancellationToken
cấu trúc cho cả phương thức khởi tạo ActionBlock<TInput>
và Task.Delay
phương thức ; nếu quá trình bị hủy bỏ, việc hủy bỏ sẽ diễn ra ở cơ hội có thể đầu tiên.
Từ đó, thật dễ dàng cấu trúc lại mã của bạn để lưu trữ ITargetBlock<DateTimeoffset>
giao diện được triển khai bởi ActionBlock<TInput>
(đây là phần trừu tượng cấp cao hơn đại diện cho các khối là người tiêu dùng và bạn muốn có thể kích hoạt mức tiêu thụ thông qua lệnh gọi đến Post
phương thức mở rộng):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
StartWork
Phương pháp của bạn :
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);
}
Và sau đó là StopWork
phương pháp của bạn :
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;
}
Tại sao bạn muốn sử dụng TPL Dataflow ở đây? Một vài lý do:
Tách mối quan tâm
Các CreateNeverEndingTask
phương pháp hiện nay là một nhà máy tạo ra "dịch vụ" của bạn như vậy để nói chuyện. Bạn kiểm soát khi nào nó bắt đầu và dừng lại, và nó hoàn toàn khép kín. Bạn không phải đan xen quyền kiểm soát trạng thái của bộ hẹn giờ với các khía cạnh khác của mã của bạn. Bạn chỉ cần tạo khối, khởi động nó và dừng nó khi bạn hoàn thành.
Sử dụng hiệu quả hơn các luồng / nhiệm vụ / tài nguyên
Bộ lập lịch mặc định cho các khối trong luồng dữ liệu TPL giống nhau đối với a Task
, là nhóm luồng. Bằng cách sử dụng ActionBlock<TInput>
để xử lý hành động của bạn, cũng như một lệnh gọi Task.Delay
, bạn đang cấp quyền kiểm soát chuỗi mà bạn đang sử dụng khi bạn không thực sự làm bất cứ điều gì. Đúng vậy, điều này thực sự dẫn đến một số chi phí khi bạn sinh ra cái mới Task
sẽ xử lý tiếp tục, nhưng điều đó sẽ nhỏ, vì bạn không xử lý điều này trong một vòng lặp chặt chẽ (bạn đang đợi mười giây giữa các lần gọi).
Nếu DoWork
hàm thực sự có thể được thực hiện ở trạng thái chờ đợi (cụ thể là nó trả về a Task
), thì bạn có thể (có thể) tối ưu hóa điều này hơn nữa bằng cách tinh chỉnh phương thức gốc ở trên để lấy a Func<DateTimeOffset, CancellationToken, Task>
thay vì a Action<DateTimeOffset>
, như sau:
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;
}
Tất nhiên, sẽ là một thực tiễn tốt để đưa CancellationToken
ra phương pháp của bạn (nếu nó chấp nhận một phương pháp), được thực hiện ở đây.
Điều đó có nghĩa là sau đó bạn sẽ có một DoWorkAsync
phương thức với chữ ký sau:
Task DoWorkAsync(CancellationToken cancellationToken);
Bạn sẽ phải thay đổi (chỉ một chút và bạn sẽ không phải lo lắng về việc tách biệt các mối quan tâm ở đây) StartWork
phương pháp tính cho chữ ký mới được chuyển cho CreateNeverEndingTask
phương thức, như sau:
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);
}