我有一个负责监听JMS消息的Windows服务。我给出了实现细节的简化版本。当消息到达时,它们被移交给处理到不同的任务(线程)并在BlockingCollection的帮助下限制最大数量的任务。有一个重试机制可以重试,直到处理成功,每次重试或最大重试尝试之间的延迟都会耗尽。重试机制的原因是为了处理使用这些消息的旧应用程序中的问题。传统系统是使用悲观锁定构建的,有时消息的处理会导致错误,最终会在几次重试尝试后终止。由于成本效益分析,决定不解决遗留系统中的问题,因为这些应用将在2到3年内被替换。
此重试机制在负责处理消息处理的同一任务线程上运行。最初我使用Thread.Sleep在每次重试尝试之间引入延迟。它工作正常,但是当我尝试关闭Windows服务时,如果当前正在处理消息并等待重试,则需要更长的时间。
然后我开始冒险实现一种方法来取消等待机制,如果触发了关机事件。
我使用了两种不同的方法。
选项#1 一个使用ManualResetEvent,当我必须等待时,我有以下代码(只发布相关的代码块)
private readonly ManualResetEvent _lockEvent = new ManualResetEvent(false);
if (_lockEvent.WaitOne(TimeSpan.FromMilliseconds(120000)))
{
Log.Info($"Thread interrupted. Retrying will resume after windows service restarts for message id {messageId}");
return;
}
当发生关闭事件时,我取消了cancellationTokenSource并设置了ManualResetEvent。一切似乎都做我想要的。它只是我必须做两个操作,以便任何依赖于CancellationToken的代码知道优雅地取消并优雅地打破重试等待。
_subscriberCancellationTokenSource.Cancel();
_lockEvent.Set();
选项#2 升级到.Net 4.6后,我开始在任何地方使用任务类型。我意识到,我也可以使用Task来实现延迟,所以这里是我试过的简化版代码
private void WaitBeforeRetrying(CancellationToken cancellationToken)
{
var waitingTask = Task.Delay(120000, cancellationToken);
waitingTask.Wait(cancellationToken);
}
我需要延迟,我只需通过传递CancellationToken
来调用该方法WaitBeforeRetrying(SubscriberCancellationToken);
当发生关闭事件时,我只是在CancellationTokenSource上调用cancel,一切都正常关闭。
_subscriberCancellationTokenSource.Cancel();
选项1和选项2似乎都在完成工作。
选项2对选项1有任何缺点吗?还有比我到目前为止更好的选择吗?真的很感激任何意见。
更新 在阅读了@EricLippert的评论之后,我理解了我做错了什么。我的大多数线程都处于等待状态,而不是实际进行任何生产性工作。这是在同步工作流中喷洒少量异步调用的结果。
我现在修改了我的延迟方法如下
private async Task WaitBeforeRetrying(CancellationToken cancellationToken)
{
await Task.Delay(120000, cancellationToken);
}
我将其作为
调用await WaitBeforeRetrying(SubscriberCancellationToken);
然后重构其余的代码,将异步机制一直传播到顶层。如果我不必等待,它不仅有助于轻松取消延迟,而且还防止线程不必要地处于阻塞状态。真的很感激每一个人的反馈。
答案 0 :(得分:3)
我不认为他们有任何不同。两者的结果是线程被阻塞直到时间用完。
如果你在ASP.NET中使用它,那么阻塞线程不是一件好事。在这种情况下,您可以制作方法async
并使用await Task.Delay
。这将在延迟之后恢复代码,但允许线程同时处理其他事情。
private async Task WaitBeforeRetrying(CancellationToken cancellationToken)
{
await Task.Delay(120000, cancellationToken);
}