25

我有一个用 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
}

编辑以响应线程清理答案

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

IsBackround 标志是一个有趣的检查。尽管如此,我怎样才能知道我是否有任何前台线程在运行?我将不得不检查创建线程的代码的每个部分。有没有其他方法,也许是一个可以帮助我找到这个的工具。

但最终,该过程确实停止了。似乎我只需要等待一些东西。但是,如果我在 OnStop 方法中等待 X 时间,则该过程大约需要 30 秒 + X 才能停止。无论我尝试做什么,在 OnStop 返回后,该过程似乎需要大约 30 秒(并不总是 30 秒,它可能会有所不同)才能真正停止。

4

7 回答 7

18

一旦您的OnStop()回调返回,停止服务的调用就会返回。根据您所展示的内容,您的OnStop()方法并没有做太多,这就解释了为什么它返回得如此之快。

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

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

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

于 2009-10-07T14:25:16.693 回答
11

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

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

这是我过去所做的:

  1. 创建一个名为 Stop 的全局 bool 标志,在服务启动时设置为 false。
  2. 调用 OnStop 方法时,将 Stop 标志设置为 true,然后对所有未完成的工作线程执行 Thread.Join。
  3. 每个工作线程负责检查停止标志,并在它为真时干净地退出。此检查应经常进行,并且始终在长时间运行的操作之前进行,以避免它延迟服务关闭太久。
  4. 在 OnStop 方法中,Join 调用也有一个超时,以给线程一个有限的时间来干净地退出......之后你只需中止它。

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

于 2009-10-06T21:44:39.343 回答
1

执行此操作的简单方法可能如下所示:
-first crete 一个全局事件

ManualResetEvent 关闭事件;

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

shutdownEvent = new ManualResetEvent(false);

-在服务停止事件

shutdownEvent.Set();

不要忘记等待线程结束

做
{
 //向服务管理器发送消息以获得更多时间
 //控制等待线程停止的时间
}
而(not_all_threads_stopped);

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

if (shutdownEvent.WaitOne(delay, true)) 中断;
于 2009-10-07T15:59:53.383 回答
0

发出您的线程循环退出的信号,将其清理干净并执行线程加入-s ..寻找问题所在的度量/秒表需要多长时间。避免因各种原因中止关机。

于 2009-10-06T21:54:01.230 回答
0

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

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

如果没有更多关于你在做什么的信息,就无法具体告诉你为什么需要 30 秒,但这可能是超时。

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

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

于 2009-10-06T21:58:44.537 回答
0

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

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

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

于 2014-03-07T09:35:27.403 回答
0

马特戴维斯非常完整。
几点; 如果你有一个永远运行的线程(因为它有一个近乎无限的循环和一个包罗万象)并且你的 service 的工作是运行那个线程,你可能希望它是一个前台线程。

此外,如果您的任何任务正在执行更长的操作,例如 sproc 调用,因此您的加入超时需要更长一点,您实际上可以要求 SCM 有更多时间关闭。请参阅:https ://msdn.microsoft.com/en-us/library/system.serviceprocess.servicebase.requestadditionaltime(v= vs.110).aspx 这对于避免可怕的“标记为删除”状态很有用。最大值是在注册表中设置的,所以我通常会要求线程通常关闭的最大预期时间(并且永远不会超过 12 秒)。请参阅:Windows 服务等待处理停止请求的最长时间以及如何请求额外时间

我的代码看起来像:

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
       }
   }
}
于 2015-09-29T15:37:50.977 回答