ฉันจะใช้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);
}