c# - 如何在基于 C# 的 Windows 服务中处理以不同时间间隔并行运行的多个任务?
问题描述
我已经有一些在 Windows 中使用线程的经验,但大部分经验来自在 C/C++ 应用程序中使用 Win32 API 函数。然而,当谈到 .NET 应用程序时,我经常不确定如何正确处理多线程。有线程、任务、TPL 和其他各种我可以用于多线程的东西,但我不知道何时使用这些选项中的哪一个。我目前正在开发基于 C# 的 Windows 服务,该服务需要定期验证来自不同数据源的不同数据组。实现验证本身对我来说并不是一个真正的问题,但我不确定如何处理同时运行的所有验证。我需要一个解决方案,它允许我做以下所有事情:
- 以不同的(预定义的)间隔运行验证。
- 从一个地方控制所有不同的验证,以便我可以在必要时暂停和/或停止它们,例如当用户停止或重新启动服务时。
- 尽可能有效地使用系统资源以避免性能问题。
到目前为止,我只有一个类似的项目,在此之前我只是将Thread
对象与 aManualResetEvent
和Thread.Join
带有超时的调用结合使用来通知线程何时停止服务。这些线程内部定期执行某些操作的逻辑如下所示:
while (!shutdownEvent.WaitOne(0))
{
if (DateTime.Now > nextExecutionTime)
{
// Do something
nextExecutionTime = nextExecutionTime.AddMinutes(interval);
}
Thread.Sleep(1000);
}
虽然这确实按预期工作,但我经常听说像这样直接使用线程被认为是“老派”甚至是一种不好的做法。我还认为这个解决方案不能非常有效地使用线程,因为它们大部分时间都在睡觉。我怎样才能以更现代和更有效的方式实现这样的目标?如果这个问题太模糊或基于意见,请告诉我,我会尽力使其尽可能具体。
解决方案
问题感觉有点宽泛,但我们可以使用提供的代码并尝试改进它。
事实上,现有代码的问题在于,在大多数情况下,它使线程处于阻塞状态,而没有做任何有用的事情(休眠)。此外,线程每秒唤醒一次只是为了检查间隔,并且在大多数情况下再次进入睡眠状态,因为它还不是验证时间。为什么这样做?shutdownEvent
因为如果您要睡更长的时间-您可能会在发出信号然后加入线程时阻塞很长时间。Thread.Sleep
不提供根据请求中断的方法。
为了解决这两个问题,我们可以使用:
CancellationTokenSource
+形式的合作取消机制CancellationToken
。Task.Delay
而不是Thread.Sleep
.
例如:
async Task ValidationLoop(CancellationToken ct) {
while (!ct.IsCancellationRequested) {
try {
var now = DateTime.Now;
if (now >= _nextExecutionTime) {
// do something
_nextExecutionTime = _nextExecutionTime.AddMinutes(1);
}
var waitFor = _nextExecutionTime - now;
if (waitFor.Ticks > 0) {
await Task.Delay(waitFor, ct);
}
}
catch (OperationCanceledException) {
// expected, just exit
// otherwise, let it go and handle cancelled task
// at the caller of this method (returned task will be cancelled).
return;
}
catch (Exception) {
// either have global exception handler here
// or expect the task returned by this method to fail
// and handle this condition at the caller
}
}
}
现在我们不再持有线程,因为await Task.Delay
不这样做。相反,在特定的时间间隔之后,它将在空闲线程池线程上执行后续代码(这比这更复杂,但我们不会在这里详细介绍)。
我们也不需要无缘无故地每秒唤醒,因为Task.Delay
接受取消令牌作为参数。当该令牌发出信号时 -Task.Delay
将立即被异常中断,这是我们所期望的并从验证循环中中断。
要停止提供的循环,您需要使用CancellationTokenSource
:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
然后将其_cts.Token
令牌传递给提供的方法。然后,当您想发出令牌信号时,只需执行以下操作:
_cts.Cancel();
为了进一步改进资源管理 - 如果您的验证代码使用任何 IO 操作(从磁盘、网络、数据库访问等读取文件) - 使用Async
所述操作的版本。然后在执行 IO 时,您将不会保留任何不必要的线程阻塞等待。
现在您不再需要自己管理线程,而是根据需要执行的任务进行操作,让框架\操作系统为您管理线程。
推荐阅读
- android - 为响应出现的 android 导航插图做一些事情
- java - 错误 404:请求的资源 [/] 不可用
- django - 文件管理器字段未在 Django-admin 中返回任何图像
- git - 做这个特定的合并会删除常见的提交而不是重新应用它们吗?
- c - CodeBlocks 调试器不工作。创建进程时出错(错误 2)
- db2 - 执行 COBOL 程序静态调用 COBOL/DB2 子程序所涉及的步骤
- java - 递增字符串时如何保持前导零
- swagger - 如何在 Swagger 中重用模式
- c++ - 条件变量 wait_for() 也在内部以系统时钟运行?
- javascript - 在 IE 上的文本输入中清除图标事件