【问题标题】:Looping infinitely using threads in Windows Service使用 Windows 服务中的线程无限循环
【发布时间】:2017-06-24 23:52:04
【问题描述】:

我在 SO 上看到了几篇与我的问题类似的帖子,但没有一个能解决我的问题。我正在创建一个 Windows 服务,它将每隔几秒钟左右轮询一次 Redis 数据库并根据结果执行操作。我想创建一个“线程池”,这样如果我在处理另一个命令(在另一个线程上)时从 Redis 获得结果,我可以同时运行多个操作。

我的一个主要问题是,当我停止我的 Windows 服务时,该进程仍然保持活动状态约 30 秒左右,而不是关闭。以下是相关代码sn-ps:

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 的传入查询,但我遇到了进程无法正确关闭的问题,如上所述。我也不确定这是否是应对这种情况的最佳方法。我喜欢一些关于更好或更“惯用”的方法来处理这个线程问题的指示。

谢谢!

【问题讨论】:

    标签: c# multithreading windows-services


    【解决方案1】:

    处理 Windows 服务的更好方法是将整个处理转移到后台任务中。这将使您能够更优雅地处理启动和关闭。

    如果你使用 Task 来模拟轮询,那么你可以使用 CancellationToken 将关闭事件传播到其他处理层。在这里您可以找到如何使用 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 服务有一个很好的了解。根据您的需要制作必要的 tweeks。让自己熟悉 c# 任务库。

    【讨论】:

      【解决方案2】:

      您是否考虑过使用布尔/二进制标志来确定服务是否实际上正在运行?或者也许在循环开始时执行一个调用来检查?我对 C# 不够熟悉,无法完全理解手头的整个任务,但我知道当涉及多线程时,二进制/布尔标志平均相当稳定。

      例如,我玩了一个使用 C# 的 Beta(太空工程师)Steam 游戏,它似乎一直存在多线程错误和每次执行后清除父数据的问题,但 Steam 创意工坊上的 Mod 作者有倾向于使用布尔和二进制标志来确保他们的任务不会卡住或崩溃,因为重新启动游戏的加载时间非常可怕,因此他们试图尽可能避免崩溃。

      它可能不是花式的,但只要你确保它不会造成失控的内存泄漏,你应该没问题。我建议,如果为每个任务的唯一标识符使用增量变量,则在某处显式设置上限,当达到所述限制时,它将调用该任务并将增量变量重置为零(有很多缓冲区空间防止意外数据丢失)。

      如果任务正在运行,它将执行调用,设置布尔值并执行,可能需要另一个调用来验证任务是否仍在运行,然后再尝试写入目标,因为我假设没有任务, Information 什么都不做,如果 Task 没有运行,它将深入到 if != isRunning 并被发送到正确的目的地以杀死线程。

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

      【讨论】:

        猜你喜欢
        • 2014-10-24
        • 1970-01-01
        • 1970-01-01
        • 2012-11-14
        • 1970-01-01
        • 2014-11-12
        • 1970-01-01
        • 1970-01-01
        • 2020-08-13
        相关资源
        最近更新 更多