如何在服务停止时停止多线程Windows服务中的工作线程

时间:2009-04-01 10:51:01

标签: .net multithreading windows-services

我有一个Windows服务,它使用生产者/消费者队列模型,多个工作线程处理队列中的任务。这些任务可以运行很长时间,如果不是几小时,则按照很多分钟的顺序运行,不涉及循环

我的问题是关于处理服务停止的最佳方法,以优雅地结束对这些工作线程的处理。我在另一个SO question中读到使用thread.Abort()是设计不良的标志,但似乎服务OnStop()方法在服务终止之前只有有限的时间完成。我可以在catchA中为ThreadAbortException做足够的清理(没有不一致状态的危险)所以在工作线程上调用thread.Abort()对我来说似乎没问题。是吗?有哪些替代方案?

7 个答案:

答案 0 :(得分:6)

确实应该避免使用Abort。最好给他们一些时间优雅地退出 - 然后也许在超时后,也许考虑中止他们 - 但最终,服务停止可以通过杀死进程来做到这一点。

我会尝试在队列中显示“刷新并退出”的信号 - 非常类似于Close方法here,但在完成后会显示某种信号。

如果诉诸Abort - 请考虑致命受伤的过程。尽快杀死它。

答案 1 :(得分:5)

为“shutdown”创建一个任务类型,并为每个工作线程将其注入生产者/消费者队列一次。

然后使用Thread.Join(超时)确保关闭已完成。

答案 2 :(得分:2)

使用.NET 4.0,您可以利用System.Threading.Tasks命名空间来利用Task对象。简而言之,您可以为任务分配CancellationToken以更优雅地处理取消/中止,无论是长期还是短期。

有关MSDN的详细信息,请参阅here

答案 3 :(得分:1)

实际修改的问题实际上与线程无关,而与如何停止长时间运行有关。我个人总是使用APM进行冗长的流和通信活动,如大文件传输。每个回调都在IO完成池线程上运行,并快速完成,处理适度的块并安排下一次传递。只需通过调用套接字对象上的Close()即可取消挂起操作。这比DIY线程管理更便宜,更有效率。


正如已经提到的,Abort()是不好的业力,应该避免。

以下材料是在将循环案例排除在问题之前编写的。

当长时间运行的进程循环时,它们应该 all 在其循环条件中包含一个退出标志,以便您可以发出信号退出。

bool _run; //member of service class

//in OnStart
_run = true;

//in a method on some other thread
while ((yourLoopCondition) & _run) 
{ 
  //do stuff
  foreach (thing in things) 
  {
    //do more stuff
    if (!_run) break;
  }
}
if (!_run) CleanUp();

//in OnStop
_run = false;

严格来说,你应该使用挥发物,但由于只有控制逻辑设定旗帜才无关紧要。从技术上讲,存在一种竞争条件,但这只意味着你可能会再次出现这种情况。

答案 4 :(得分:0)

使用ManualResetEvent检查事件是否已发出信号,请参阅Thread Synchronization (C# Programming Guide)上的示例

答案 5 :(得分:0)

这是我用来停止Windows服务中的线程的代码(请注意,我直接使用Threads而不是使用线程池):

// signal all threads to stop
this.AllThreadsStopSignal.Set();

if (logThreads.IsDebugEnabled)
    logThreads.Debug ("Stop workers");

// remember the time of the signal
DateTime signalTime = DateTime.Now;

// create an array of workers to be stopped
List<IServiceWorker> workersToBeStopped = new List<IServiceWorker> (workers);

while (true)
{
    // wait for some time
    Thread.Sleep (1000);

    // go through the list and see if any workers have stopped
    int i = 0;
    while (i < workersToBeStopped.Count)
    {
        IServiceWorker workerToBeStopped = workersToBeStopped [i];

        if (log.IsDebugEnabled)
            log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                "Stopping worker '{0}'. Worker state={1}",
                workerToBeStopped.WorkerDescription,
                workerToBeStopped.WorkerState));

        bool stopped = workerToBeStopped.JoinThread (TimeSpan.Zero);

        // if stopped, remove it from the list
        if (stopped)
        {
            workersToBeStopped.RemoveAt (i);
            if (log.IsDebugEnabled)
                log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                    "Worker '{0}' was stopped.", workerToBeStopped.WorkerDescription));
        }
        else
        {
            i++;
            if (log.IsDebugEnabled)
                log.Debug (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                    "Worker '{0}' could not be stopped, will try again later. Worker state={1}",
                    workerToBeStopped.WorkerDescription,
                    workerToBeStopped.WorkerState));
        }
    }

    // if all workers were stopped, exit from the loop
    if (workersToBeStopped.Count == 0)
        break;

    // check if the duration of stopping has exceeded maximum time
    DateTime nowTime = DateTime.Now;
    TimeSpan duration = nowTime - signalTime;

    if (duration > serviceCustomization.ThreadTerminationMaxDuration)
    {
        // execute forced abortion of all workers which have not stopped
        foreach (IServiceWorker worker in workersToBeStopped)
        {
            try
            {
                log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                    "Aborting worker '{0}.", worker.WorkerDescription));
                worker.Abort ();
                log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                    "Worker '{0}' aborted.", worker.WorkerDescription));
            }
            catch (ThreadStateException ex)
            {
                log.Warn (String.Format (System.Globalization.CultureInfo.InvariantCulture,
                    "Worker '{0}' could not be aborted.", worker.WorkerDescription), ex);
            }
        }
        break;
    }
}

答案 6 :(得分:0)

如果您的进程正在关闭,我个人看不到使用Abort()的问题。我会尝试找另一种方法,但最后,无论如何如果你将在主线程中进行清理无关紧要。

另一种选择是将工作线程标记为background个线程。这样,它们会在进程关闭时自动关闭。您可以在退出之前使用AppDomain.ProcessExit事件进行清理。