如何正确停止多线程.NET Windows服务?

时间:2009-10-06 21:29:10

标签: .net windows-services process terminate

我有一个用C#编写的Windows服务,它创建了一堆卡车线程并建立了许多网络连接(WMI,SNMP,简单的TCP,http)。尝试使用服务MSC管理单元停止Windows服务时,停止服务的调用返回相对较快,但该过程继续运行约30秒左右。

主要问题是它可能需要30秒以上才能停止。我能找到什么,我该如何寻找它?

第二个问题是,即使进程仍在运行,为什么服务msc管理单元(服务控制器)也会返回。有没有办法让它只在进程实际被杀死时返回?

以下是服务的OnStop方法中的代码

protected override void OnStop()
{
   //doing some tracing
   //......

   //doing some minor single threaded cleanup here
   //......

   base.OnStop();

   //doing some tracing here
}

编辑以响应线程清理答案

你们中的许多人已经回答我应该跟踪我所有的线程,然后将它们清理干净。我不认为这是一种实用的方法。首先,我无法访问一个位置的所有托管线程。该软件非常庞大,包含不同的组件,项目甚至第三方dll都可以创建线程。我无法在一个位置跟踪所有这些,或者有一个所有线程检查的标志(即使我可以让所有线程检查一个标志,许多线程阻塞信号量之类的东西。当它们阻塞时它们可以检查。我将不得不让他们等待超时,然后再检查这个全局标志和等待。

IsBackround标志是一个有趣的事情要检查。但是,我怎么能知道我是否有运行arround的forground线程?我将不得不检查创建线程的代码的每个部分。还有其他方法,也许是一种可以帮助我找到答案的工具。

最终,这个过程确实停止了。我似乎只需要等待一些东西。但是,如果我在OnStop方法中等待X ammount时间,那么它将花费大约30秒+ X来停止。无论我尝试做什么,在OnStop返回以使进程实际停止之后,过程似乎需要大约30秒(它不总是30秒,它可以变化)。

7 个答案:

答案 0 :(得分:18)

一旦OnStop()回调返回,停止服务的调用就会返回。根据您展示的内容,您的OnStop()方法做得不多,这就解释了为什么它返回的速度很快。

有几种方法可以让您的服务退出。

首先,您可以重新编写OnStop()方法,以指示所有线程关闭并等待它们在退出之前关闭。正如@DSO建议的那样,您可以使用全局bool标志来执行此操作(确保将其标记为volatile)。我通常使用ManualResetEvent,但两者都可以。发出线程信号退出。然后加入具有某种超时期限的线程(我通常使用3000毫秒)。如果线程仍未退出,则可以调用Abort()方法退出它们。一般来说,Abort()方法是不受欢迎的,但鉴于您的流程无论如何都在退出,这并不是什么大问题。如果您一直有一个必须中止的线程,您可以重做该线程以更好地响应关闭信号。

其次,将您的线程标记为background个线程(有关详细信息,请参阅here)。听起来您正在为线程使用System.Threading.Thread类,默认情况下这是前台线程。这样做可以确保线程不会阻止进程退出。如果您只执行托管代码,这将正常工作。如果你有一个等待非托管代码的线程,我不确定设置IsBackground属性是否仍会导致线程在关闭时自动退出,即你可能仍然需要修改线程模型以使这个线程响应你的关机请求。

答案 1 :(得分:11)

从OnStop返回时,服务控制管理器(SCM)将返回。因此,您需要修复OnStop实现以阻止所有线程完成。

一般方法是让OnStop发出所有线程停止信号,然后等待它们停止。为了避免无限期阻塞,你可以给线程一个停止时间限制,如果它们花费的时间太长就会中止它们。

以下是我过去所做的事情:

  1. 创建一个名为的全局bool标志 停止,在服务时设置为false 已经开始了。
  2. 调用OnStop方法时,将Stop标志设置为true,然后对所有未完成的工作线程执行Thread.Join。
  3. 每个工作线程负责检查Stop标志,并在其为真时立即退出。此检查应经常进行,并且始终在长时间运行之前进行,以避免延迟服务停机太长时间。
  4. 在OnStop方法中,在Join调用上也有一个超时,为线程提供一个有限的时间来干净地退出...之后你只是中止它。
  5. 注意在#4中,你应该给你的线程留出足够的时间在正常情况下退出。中止应该只发生在线程挂起的异常情况下......在这种情况下,执行中止并不比用户或系统终止进程(后者如果计算机正在关闭)更糟糕。

答案 2 :(得分:1)

这样做的简单方法可能如下所示:
- 第一次克里特岛是一场全球性的活动

ManualResetEvent shutdownEvent;

-at service start创建手动重置事件并将其设置为初始状态为无信号

shutdownEvent = new ManualResetEvent(false);

-at服务停止事件

shutdownEvent.Set();

不要忘记等待线程结束

do
{
 //send message for Service Manager to get more time
 //control how long you wait for threads stop
}
while ( not_all_threads_stopped );

- 每个线程必须不时测试,事件停止

if ( shutdownEvent.WaitOne(delay, true) ) break;

答案 3 :(得分:0)

发出你的线程循环退出信号,干净并做线程Join-s ..查看作为测量/秒表问题所需的时间。由于各种原因避免中断关机..

答案 4 :(得分:0)

回答第一个问题(为什么服务会继续运行30秒以上): 原因很多。例如,在使用WCF时,停止主机会导致进程停止接受传入请求,并等待在停止之前处理所有当前请求。

对于其他类型的网络操作也是如此:操作将在终止之前尝试完成。这就是为什么大多数网络请求都有一个内置的超时值,用于请求可能“挂起”(服务器出现故障,网络问题等)。

如果没有更多关于你究竟在做什么的信息,那么就没有办法告诉你为什么它需要30秒,但这可能是暂停。

回答第二个问题(为什么服务控制器返回):我不确定。我知道ServiceController类有一个WaitForState方法,允许你等到达到给定的状态。服务控制器可能正在等待预定时间(另一个超时),然后强行终止您的应用程序。

也很可能已经调用了base.OnStop方法,并且OnStop方法已经返回,向ServiceController发出信号表明进程已经停止,而实际上有一些线程没有停止。你有责任对这些线程进行判断。

答案 5 :(得分:0)

对于像我一样寻找缩短结束时间的解决方案的人,请尝试设置ServiceHost的CloseTimeout。

现在我正在努力理解为什么在没有它的情况下需要花费这么多时间才停下来我也认为这是线程问题。我确实查看了Visual Studio,附加到服务并停止它:我的服务启动了一些仍在运行的线程。

现在的问题是:这些线程真的让我的服务停止这么慢吗?微软没想过吗?难道你不认为它可能是一个端口发布问题或其他什么?因为处理线程sto是浪费时间,最后没有更短的关闭时间。

答案 6 :(得分:0)

马特戴维斯非常完整 几点; 如果你有一个永远运行的线程(因为它有一个接近无限循环和一个全部捕获)并且你的服务的工作是运行该线程,你可能希望它是一个前台线程。

此外,如果您的任何任务正在执行更长时间的操作(例如,sproc调用),因此您的加入超时需要更长时间,您实际上可以要求SCM有更多时间关闭。请参阅:https://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v=vs.110).aspx 这可以用于避免可怕的删除"标记为删除"状态。最大值在注册表中设置,因此我通常会请求线程通常关闭的最大预期时间(并且不会超过12秒)。请参阅:what is the maximum time windows service wait to process stop request and how to request for additional time

我的代码类似于:

private Thread _worker;       
private readonly CancellationTokenSource _cts = new CancellationTokenSource(); 

protected override void OnStart(string[] args)
{
    _worker = new Thread(() => ProcessBatch(_cts.Token));
    _worker.Start();             
}

protected override void OnStop()
{            
    RequestAdditionalTime(4000);
    _cts.Cancel();            
    if(_worker != null && _worker.IsAlive)
        if(!_worker.Join(3000))
            _worker.Abort(); 
}

private void ProcessBatch(CancellationToken cancelToken)
{
   while (true)
   {
       try
       {
           if(cancelToken.IsCancellationRequested)
                return;               
           // Do work
           if(cancelToken.IsCancellationRequested)
                return;
           // Do more work
           if(cancelToken.IsCancellationRequested)
                return;
           // Do even more work
       }
       catch(Exception ex)
       {
           // Log it
       }
   }
}