如何在基于 C# 的 Windows 服务中以不同的时间间隔并行处理多个任务 运行?
How to handle multiple tasks running in parallel at different intervals inside a C# based Windows service?
我已经有一些在 Windows 中使用线程的经验,但大部分经验来自在 C/C++ 应用程序中使用 Win32 API 函数。然而,当谈到 .NET 应用程序时,我常常不确定如何正确处理多线程。有线程、任务、TPL 和其他各种我可以用于多线程的东西,但我不知道什么时候使用这些选项中的哪一个。
我目前正在开发基于 C# 的 Windows 服务,该服务需要定期验证来自不同数据源的不同数据组。实施验证本身对我来说并不是真正的问题,但我不确定如何同时处理所有验证 运行。
我需要一个解决方案,它允许我做以下所有事情:
- 运行 不同(预定义)时间间隔的验证。
- 从一个地方控制所有不同的验证,以便我可以暂停 and/or 在必要时停止它们,例如当用户停止或重新启动服务时。
- 尽可能高效地使用系统资源以避免性能问题。
到目前为止,我只有一个类似的项目,之前我只是简单地使用 Thread
对象与 ManualResetEvent
和 Thread.Join
调用结合超时来通知线程当服务停止时。这些线程内部定期执行某些操作的逻辑如下所示:
while (!shutdownEvent.WaitOne(0))
{
if (DateTime.Now > nextExecutionTime)
{
// Do something
nextExecutionTime = nextExecutionTime.AddMinutes(interval);
}
Thread.Sleep(1000);
}
虽然这确实按预期工作,但我经常听说像这样直接使用线程被认为是“老派”甚至是一种不好的做法。我还认为这个解决方案不能非常有效地使用线程,因为它们大部分时间都在休眠。我怎样才能以更现代、更高效的方式实现这样的目标?
如果这个问题太模糊或基于意见,请告诉我,我会尽力使其尽可能具体。
您应该使用 Microsoft 的 Reactive Framework(又名 Rx)- NuGet System.Reactive
并添加 using System.Reactive.Linq;
- 然后您可以这样做:
Subject<bool> starter = new Subject<bool>();
IObservable<Unit> query =
starter
.StartWith(true)
.Select(x => x
? Observable.Interval(TimeSpan.FromSeconds(5.0)).SelectMany(y => Observable.Start(() => Validation()))
: Observable.Never<Unit>())
.Switch();
IDisposable subscription = query.Subscribe();
每 5.0
秒触发 Validation()
方法。
当您需要暂停和恢复时,请执行以下操作:
starter.OnNext(false);
// Now paused
starter.OnNext(true);
// Now restarted.
当你想停止这一切时调用subscription.Dispose()
。
问题感觉有点宽泛,但我们可以使用提供的代码并尝试改进它。
确实,现有代码的问题在于,在大多数情况下,它会在不执行任何有用操作(休眠)的情况下保持线程阻塞。此外,线程每秒唤醒一次只是为了检查间隔,并且在大多数情况下再次进入睡眠状态,因为它还不是验证时间。它为什么这样做?因为如果你要睡更长的时间——当你发出 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 时,您将不会等待不必要的线程阻塞。
现在您不再需要自己管理线程,而是根据您需要执行的任务进行操作,让框架 OS 为您管理线程。
我已经有一些在 Windows 中使用线程的经验,但大部分经验来自在 C/C++ 应用程序中使用 Win32 API 函数。然而,当谈到 .NET 应用程序时,我常常不确定如何正确处理多线程。有线程、任务、TPL 和其他各种我可以用于多线程的东西,但我不知道什么时候使用这些选项中的哪一个。 我目前正在开发基于 C# 的 Windows 服务,该服务需要定期验证来自不同数据源的不同数据组。实施验证本身对我来说并不是真正的问题,但我不确定如何同时处理所有验证 运行。 我需要一个解决方案,它允许我做以下所有事情:
- 运行 不同(预定义)时间间隔的验证。
- 从一个地方控制所有不同的验证,以便我可以暂停 and/or 在必要时停止它们,例如当用户停止或重新启动服务时。
- 尽可能高效地使用系统资源以避免性能问题。
到目前为止,我只有一个类似的项目,之前我只是简单地使用 Thread
对象与 ManualResetEvent
和 Thread.Join
调用结合超时来通知线程当服务停止时。这些线程内部定期执行某些操作的逻辑如下所示:
while (!shutdownEvent.WaitOne(0))
{
if (DateTime.Now > nextExecutionTime)
{
// Do something
nextExecutionTime = nextExecutionTime.AddMinutes(interval);
}
Thread.Sleep(1000);
}
虽然这确实按预期工作,但我经常听说像这样直接使用线程被认为是“老派”甚至是一种不好的做法。我还认为这个解决方案不能非常有效地使用线程,因为它们大部分时间都在休眠。我怎样才能以更现代、更高效的方式实现这样的目标? 如果这个问题太模糊或基于意见,请告诉我,我会尽力使其尽可能具体。
您应该使用 Microsoft 的 Reactive Framework(又名 Rx)- NuGet System.Reactive
并添加 using System.Reactive.Linq;
- 然后您可以这样做:
Subject<bool> starter = new Subject<bool>();
IObservable<Unit> query =
starter
.StartWith(true)
.Select(x => x
? Observable.Interval(TimeSpan.FromSeconds(5.0)).SelectMany(y => Observable.Start(() => Validation()))
: Observable.Never<Unit>())
.Switch();
IDisposable subscription = query.Subscribe();
每 5.0
秒触发 Validation()
方法。
当您需要暂停和恢复时,请执行以下操作:
starter.OnNext(false);
// Now paused
starter.OnNext(true);
// Now restarted.
当你想停止这一切时调用subscription.Dispose()
。
问题感觉有点宽泛,但我们可以使用提供的代码并尝试改进它。
确实,现有代码的问题在于,在大多数情况下,它会在不执行任何有用操作(休眠)的情况下保持线程阻塞。此外,线程每秒唤醒一次只是为了检查间隔,并且在大多数情况下再次进入睡眠状态,因为它还不是验证时间。它为什么这样做?因为如果你要睡更长的时间——当你发出 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 时,您将不会等待不必要的线程阻塞。
现在您不再需要自己管理线程,而是根据您需要执行的任务进行操作,让框架 OS 为您管理线程。