代码之家  ›  专栏  ›  技术社区  ›  Mark

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

  •  24
  • Mark  · 技术社区  · 17 年前

    我有一个用C编写的Windows服务,它创建了大量线程并建立了许多网络连接(wmi、snmp、simple 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?我必须检查创建线程的代码的每个部分。还有别的办法吗,也许有个工具可以帮我找到答案。

    但最终,这个过程确实停止了。似乎我只需要等待一些事情。但是,如果我在onstop方法中等待x ammount的时间,那么这个过程大约需要30秒+x才能停止。无论我尝试做什么,在OnStop返回后,进程需要大约30秒(不总是30秒,可能会有所不同)才能真正停止。

    7 回复  |  直到 9 年前
        1
  •  18
  •   Tshilidzi Mudau Simon Müller    9 年前

    一旦您的 OnStop() 回调返回。根据你展示的内容, on() 方法的作用不大,这就解释了为什么它返回得这么快。

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

    首先,你可以修改 on() 方法向所有线程发出关闭的信号,并在退出前等待它们关闭。正如@dso建议的那样,您可以使用全局bool标志来执行此操作(确保将其标记为 volatile )我通常使用ManualResetEvent,但两者都可以。发出退出线程的信号。然后用某种超时时间连接线程(我通常使用3000毫秒)。如果线程到那时仍未退出,则可以调用 Abort() 方法退出它们。一般来说, 中止() 方法是不受欢迎的,但考虑到您的过程正在退出,这不是什么大问题。如果您始终有一个必须中止的线程,那么您可以重新编写该线程,以便对关闭信号作出更大的响应。

    其次,将线程标记为 background 螺纹(参见 here 了解更多详细信息)。听起来您正在为线程使用System.Threading.Thread类,默认情况下,这些线程是前台线程。这样做将确保线程不会阻止进程退出。如果只执行托管代码,这将很好地工作。如果有一个线程正在等待非托管代码,我不确定设置isbackground属性是否仍会导致该线程在关闭时自动退出,也就是说,您可能仍然需要重新编写线程模型,使该线程响应关闭请求。

        2
  •  11
  •   dso    17 年前

    当您从onstop返回时,服务控制管理器(SCM)将返回。因此,您需要修复onstop实现来阻塞,直到所有线程都完成。

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

    以下是我过去所做的:

    1. 创建一个名为 停止,当服务 开始了。
    2. 调用OnStop方法时,将Stop标志设置为true,然后执行线程。在所有未完成的工作线程上联接。
    3. 每个工作线程负责检查停止标志,如果为真,则干净地退出。这种检查应该经常进行,并且总是在长时间运行操作之前进行,以避免让它延迟服务关闭太长时间。
    4. 在onstop方法中,在join调用上也有一个超时,以便给线程一个有限的时间来干净地退出…然后你就放弃它。

    注意在4中,在正常情况下,您应该给线程足够的时间退出。只有在线程挂起的异常情况下才应中止…在这种情况下,中止并不比用户或系统终止进程(如果计算机正在关闭,则终止进程)更糟糕。

        3
  •  1
  •   lsalamon    17 年前

    这样做的简单方法如下:
    -第一次克里特岛全球活动

    ManualResetEvent shutdownEvent;

    -在服务启动时,创建手动重置事件并将其设置为未签名的初始状态
    shutdownEvent = new ManualResetEvent(false);

    -在服务停止事件时

    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;
        4
  •  0
  •   rama-jka toti    17 年前

    发出线程循环退出的信号,进行清理并执行线程连接-S。在出现问题的地方,查看作为测量/秒表需要多长时间。避免因各种原因导致的停机失败。

        5
  •  0
  •   Blue Toque    17 年前

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

    其他类型的网络操作也同样如此:操作将尝试在终止之前完成。这就是为什么大多数网络请求在请求可能“挂起”(服务器故障、网络问题等)时具有内置超时值的原因。

    如果没有更多关于你到底在做什么的信息,就没有办法确切地告诉你为什么要花30秒,但这可能是一个超时。

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

    也很可能调用了base.onstop方法,并且onstop方法已返回,向ServiceController发出进程已停止的信号,而实际上有一些线程尚未停止。您负责终止这些线程。

        6
  •  0
  •   Chaos    12 年前

    对于看起来像我这样的人,如果想要缩短关闭时间的解决方案,请尝试设置服务主机的closeTimeout。

    现在我试图理解为什么没有它要花那么多时间才能停下来,我也认为这是线程问题。我确实在Visual Studio中查找了附加到服务并停止了它:我的服务启动了一些仍在运行的线程。

    现在的问题是:真的是这些线程让我的服务停止得这么慢吗?微软没有考虑过吗?你不认为这可能是端口释放问题或其他问题吗?因为处理线程s to是浪费时间的,最终没有更短的关闭时间。

        7
  •  0
  •   Community Mohan Dere    9 年前

    马特·戴维斯很完美。
    几点; 如果有一个线程永远运行(因为它有一个近乎无限的循环和一个catch all),并且您的服务的任务是运行该线程,那么您可能希望它是一个前台线程。

    此外,如果您的任何任务正在执行更长的操作(如存储过程调用),因此连接超时需要更长一点,那么您实际上可以请求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
           }
       }
    }
    
    推荐文章