ฉันจะใช้TPL Dataflowสำหรับสิ่งนี้ (เนื่องจากคุณใช้. NET 4.5 และใช้Taskภายใน) คุณสามารถสร้างActionBlock<TInput>รายการที่โพสต์เป็นของตัวเองได้อย่างง่ายดายหลังจากดำเนินการแล้วและรอระยะเวลาที่เหมาะสม
ขั้นแรกสร้างโรงงานที่จะสร้างงานที่ไม่มีวันสิ้นสุดของคุณ:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Action<DateTimeOffset> action, CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
ActionBlock<DateTimeOffset> block = null;
block = new ActionBlock<DateTimeOffset>(async now => {
action(now);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
ConfigureAwait(false);
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
return block;
}
ฉันเลือกActionBlock<TInput>ที่จะใช้DateTimeOffsetโครงสร้าง ; คุณต้องส่งพารามิเตอร์ type และมันอาจผ่านสถานะที่มีประโยชน์บางอย่าง (คุณสามารถเปลี่ยนลักษณะของสถานะได้หากต้องการ)
นอกจากนี้ยังทราบว่าActionBlock<TInput>กระบวนการเริ่มต้นเพียงหนึ่งรายการในเวลาดังนั้นคุณจะรับประกันว่ามีเพียงหนึ่งการกระทำจะถูกประมวลผล (หมายถึงคุณจะไม่ต้องจัดการกับreentrancyเมื่อมันเรียกPostวิธีขยายกลับมาที่ตัวเอง)
ฉันยังส่งผ่านCancellationTokenโครงสร้างไปยังทั้งตัวสร้างของActionBlock<TInput>และไปยังTask.Delayการเรียกใช้เมธอด หากกระบวนการถูกยกเลิกการยกเลิกจะเกิดขึ้นในโอกาสแรกที่เป็นไปได้
จากนั้นการปรับโครงสร้างโค้ดของคุณเป็นเรื่องง่ายในการจัดเก็บITargetBlock<DateTimeoffset>อินเทอร์เฟซที่ใช้งานโดยActionBlock<TInput>(นี่คือนามธรรมระดับสูงที่แสดงถึงบล็อกที่เป็นผู้บริโภคและคุณต้องการให้สามารถกระตุ้นการบริโภคผ่านการเรียกใช้Postวิธีการขยาย):
CancellationTokenSource wtoken;
ActionBlock<DateTimeOffset> task;
StartWorkวิธีการของคุณ:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask(now => DoWork(), wtoken.Token);
task.Post(DateTimeOffset.Now);
}
แล้วStopWorkวิธีการของคุณ:
void StopWork()
{
using (wtoken)
{
wtoken.Cancel();
}
wtoken = null;
task = null;
}
ทำไมคุณถึงต้องการใช้ TPL Dataflow ที่นี่ เหตุผลบางประการ:
การแยกความกังวล
CreateNeverEndingTaskวิธีการอยู่ในขณะนี้โรงงานที่สร้าง "บริการ" ของคุณเพื่อที่จะพูด คุณควบคุมได้ว่าจะเริ่มและหยุดเมื่อใดและมีอยู่ในตัวเองอย่างสมบูรณ์ คุณไม่จำเป็นต้องผสมผสานการควบคุมสถานะของตัวจับเวลากับส่วนอื่น ๆ ของรหัสของคุณ คุณเพียงแค่สร้างบล็อกเริ่มต้นและหยุดเมื่อคุณทำเสร็จแล้ว
การใช้เธรด / งาน / ทรัพยากรอย่างมีประสิทธิภาพมากขึ้น
ตัวกำหนดตารางเวลาเริ่มต้นสำหรับบล็อกในโฟลว์ข้อมูล TPL จะเหมือนกันสำหรับ a Taskซึ่งเป็นเธรดพูล ด้วยการใช้ActionBlock<TInput>เพื่อประมวลผลการกระทำของคุณเช่นเดียวกับการโทรTask.Delayคุณจะสามารถควบคุมเธรดที่คุณใช้เมื่อคุณไม่ได้ทำอะไรเลย จริงอยู่สิ่งนี้นำไปสู่ค่าใช้จ่ายบางส่วนเมื่อคุณสร้างสิ่งใหม่Taskที่จะประมวลผลความต่อเนื่อง แต่ควรมีขนาดเล็กเนื่องจากคุณไม่ได้ประมวลผลสิ่งนี้ในวงที่แน่น (คุณกำลังรอสิบวินาทีระหว่างการเรียก)
หากDoWorkฟังก์ชันนั้นสามารถรอได้จริง (กล่าวคือในกรณีที่ส่งกลับ a Task) คุณสามารถ (อาจ) เพิ่มประสิทธิภาพให้มากยิ่งขึ้นโดยการปรับแต่งวิธีการโรงงานด้านบนเพื่อใช้Func<DateTimeOffset, CancellationToken, Task>แทนAction<DateTimeOffset>:
ITargetBlock<DateTimeOffset> CreateNeverEndingTask(
Func<DateTimeOffset, CancellationToken, Task> action,
CancellationToken cancellationToken)
{
if (action == null) throw new ArgumentNullException("action");
ActionBlock<DateTimeOffset> block = null;
block = new ActionBlock<DateTimeOffset>(async now => {
await action(now, cancellationToken).
ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken).
ConfigureAwait(false);
block.Post(DateTimeOffset.Now);
}, new ExecutionDataflowBlockOptions {
CancellationToken = cancellationToken
});
return block;
}
แน่นอนว่าจะเป็นการดีที่จะสานCancellationTokenผ่านวิธีการของคุณ (ถ้ายอมรับ) ซึ่งทำได้ที่นี่
นั่นหมายความว่าคุณจะมีDoWorkAsyncวิธีการที่มีลายเซ็นต่อไปนี้:
Task DoWorkAsync(CancellationToken cancellationToken);
คุณต้องเปลี่ยน (เพียงเล็กน้อยและคุณจะไม่แยกความกังวลออกจากที่นี่) StartWorkวิธีการพิจารณาลายเซ็นใหม่ที่ส่งไปยังCreateNeverEndingTaskวิธีการดังนี้:
void StartWork()
{
wtoken = new CancellationTokenSource();
task = CreateNeverEndingTask((now, ct) => DoWorkAsync(ct), wtoken.Token);
task.Post(DateTimeOffset.Now, wtoken.Token);
}