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

时间:2017-06-24 23:52:04

标签: c# multithreading windows-services

我在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();
}

以下是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的传入查询就好了,但是如上所述我没有正确关闭进程的问题。我也不确定这是否是解决这种情况的最佳方法。我喜欢关于更好或更多"惯用"的一些指示。处理此线程问题的方法。

谢谢!

2 个答案:

答案 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#的初学者,因此我不像其他用户那样熟悉高级命令。