我在SO上发现了几个与我的问题类似的帖子,但没有一个帖子解决了我的问题。我创建了一个Windows服务,它将每隔几秒左右轮询一次Redis数据库,并根据结果执行操作。我想创建一个"线程池"如果我在处理另一个命令时(在另一个线程上)从Redis获得结果,那么我可以同时运行多个动作。
我的一个主要问题是,当我停止我的Windows服务时,该过程仍然保持活动约30秒左右,而不是关闭。以下是相关的代码段:
Thread Worker;
IDatabase db = ...;
AutoResetEvent StopRequest = new AutoResetEvent(false);
protected override void OnStart(string[] args) {
var poller = new Poller();
Worker = new Thread(() => poller.Poll(StopRequest));
Worker.Start();
}
protected override void OnStop() {
// Signal worker to stop and wait until it does
StopRequest.Set();
Worker.Join();
}
以下是Poller
类Poll
方法的示例。
public async void Poll(AutoResetEvent finished)
{
var res = string.Empty;
while (!finished.WaitOne(1000))
{
res = db.StringGet($"task");
if (!String.IsNullOrEmpty(res))
{
ParseAction(res);
}
db.KeyDelete($"task");
}
}
所以这个代码(有很多修剪过的)在后台保持正常运行,似乎处理来自Redis的传入查询就好了,但是如上所述我没有正确关闭进程的问题。我也不确定这是否是解决这种情况的最佳方法。我喜欢关于更好或更多"惯用"的一些指示。处理此线程问题的方法。
谢谢!
答案 0 :(得分:1)
处理Windows服务的更好方法是将整个处理转移到后台任务中。这将使您能够更优雅地处理启动和关闭。
如果使用Task来模拟轮询,则可以使用CancellationToken将shutdown事件传播到其他处理层。在这里,您可以找到如何使用Task模拟计时器。请阅读 Is there a Task based replacement for System.Threading.Timer?
以下是具有后台任务的Windows服务OnStart和OnStop处理程序的代码示例,可以快速启动和关闭。此代码基于.NET 4.6.1。
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.ServiceProcess;
namespace TEST.MY.SERVICE
{
partial class MyService : ServiceBase
{
private Task _initializationTask;
private CancellationTokenSource _initializationCancelTokenSource;
private CancellationToken _intitializationCancellationToken;
public MyService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
_initializationCancelTokenSource = new CancellationTokenSource();
_intitializationCancellationToken = _initializationCancelTokenSource.Token;
_initializationTask = Task.Run(() =>
{
//Kick off polling from here that also uses _intitializationCancellationToken, so that when _initializationCancelTokenSource.Cancel() is invoked from OnStop it will start cancellation chain reaction to stop all running activity. You can pass it even into your methods and check _intitializationCancellationToken.IsCancellationRequested and take appropriate actions.
//using the Task timer from the other stack overflow post, You could do something like
Task perdiodicTask = PeriodicTaskFactory.Start(() =>
{
Console.WriteLine(DateTime.Now);
//execute your logic here that has to run periodically
}, intervalInMilliseconds: 5000, // fire every 5 seconds...
cancelToken: _intitializationCancellationToken); // Using same cancellation token to manage timer cancellation
perdiodicTask.ContinueWith(_ =>
{
Console.WriteLine("Finished!");
}).Wait();
}, _intitializationCancellationToken)
.ContinueWith(t =>
{
//deal with any task related errors
},TaskContinuationOptions.OnlyOnFaulted);
}
protected override void OnStop()
{
try
{
_initializationCancelTokenSource?.Cancel();
_initializationCancelTokenSource?.Dispose();
_initializationTask?.Dispose();
}
catch (Exception stopException)
{
//log any errors
}
}
}
}
您可以在此处找到有关如何取消等待任务的更多详细信息。 https://msdn.microsoft.com/en-us/library/dd321315(v=vs.110).aspx
这应该可以让您了解如何设计Windows服务。根据您的需求进行必要的调整。熟悉c#任务库。
答案 1 :(得分:-1)
您是否考虑使用布尔/二进制标志来确定服务是否实际上正在运行?或者可能在循环开始时执行调用以进行检查?我对C#不太熟悉,无法完全理解手头的整个任务,但我知道当涉及多线程时,二进制/布尔标志平均相当稳定。
例如,我玩一个使用C#的Beta(太空工程师)的Steam游戏,并且在每次执行后似乎始终存在多线程错误和清除父数据的问题,但Steam工作室上的Mod作者已经a使用布尔和二进制标志的趋势,以确保他们的任务不会卡住或崩溃,因为重新启动游戏的加载时间是可怕的,所以他们试图避免尽可能多的崩溃。
它可能不是花哨,但只要你确保它不会造成失控的内存泄漏,你应该没问题。我建议,如果为每个任务的唯一标识符使用增量变量,则在某处明确设置上限,当达到所述限制时,它将调用该任务并将增量变量重置为零(具有大量缓冲空间到防止意外数据丢失。)
如果任务正在运行,它将执行调用,设置布尔值,并执行,可能需要另一个调用来验证任务是否仍在运行,然后尝试写入目标,因为我假设没有任务,信息什么都不做,如果任务没有运行,它将钻研if != isRunning
并被发送到正确的目的地以杀死线程。
我希望这些信息对您有所帮助,正如我之前提到的,我只是C#的初学者,因此我不像其他用户那样熟悉高级命令。