使用Windows服务中的线程无限地循环



我已经看过几篇文章,所以与我的问题类似,但是它们都没有解决我的问题。我正在创建一个Windows服务,该服务将每隔几秒钟左右对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();
}

这是PollerPoll方法的示例。

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处理传入的查询,但是我遇到的问题是,该过程的问题未正确关闭,如我所述。我也不确定这是否是这种情况的最佳方法。我会喜欢一些更好或更多的"惯用"方法来处理这个线程问题的指示。

谢谢!

处理Windows Service的更好方法是将整个处理转移到背景任务中。这将使您更优雅地处理启动和关闭。

并且,如果您使用任务模拟轮询,则可以使用comcellationToken将关闭事件传播到其他处理层。在这里,您可以找到如何使用任务模拟计时器。请阅读是否有System.Threading.Timer的基于任务的替换?

这是Windows Service 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#任务库。

您是否使用布尔/二进制标志来思考是否实际上在运行该服务?还是在循环开始时执行呼叫?我对C#不太熟悉,以便完全理解手头的整个任务,但是我知道,当涉及多线程时,二进制/布尔标志平均而言是相当稳定的。

例如,我玩的是使用C#的Beta(太空工程师(中的Steam游戏,并且在每次执行后,它似乎一直存在多线程错误和清除父级数据的问题,但是Steam Workshop上的Mod作者都有使用布尔和二进制标志以确保其任务不会卡住或崩溃的趋势,因为重新启动游戏的负载时间很恐怖,因此他们试图避免尽可能多的崩溃。

这可能不是花哨的,但是只要您确保不会造成失控的内存泄漏,就应该没事。我建议,如果为您的每个任务使用增量变量为您的唯一标识符使用增量变量,请明确设置某个地方的上限,并且当达到该限制时,它将调用该任务并将增量变量重置为零(将大量的缓冲区空间放到防止意外数据丢失(。

如果任务正在运行,它将执行呼叫,设置布尔值并执行,可能需要另一个呼叫来验证任务在尝试写入目标之前仍在运行,因为我假设没有任务,该信息无能为力,如果任务不运行,它将深入研究if != isRunning并发送到正确的目的地杀死线程。

我希望这些信息对您有帮助,正如我之前提到的,我只是C#中的初学者,所以我对此处的其他一些用户不太熟悉高级命令。

最新更新