【问题标题】:How to terminate a worker thread correctly in c#如何在 C# 中正确终止工作线程
【发布时间】:2010-09-13 08:10:55
【问题描述】:

问题陈述

我有一个工作线程,它基本上扫描一个文件夹,进入其中的文件,然后休眠一段时间。扫描操作可能需要 2-3 秒,但不会更多。我正在寻找一种优雅地停止这个线程的方法。

澄清:我想在线程睡眠而不是扫描时停止线程。但是,问题是我不知道线程的当前状态是什么。如果它正在睡觉,我希望它立即退出。如果它正在扫描,我希望它在它试图阻止的那一刻退出。

尝试解决方案

起初我使用的是睡眠和中断。然后我发现中断并没有真正中断睡眠——它只在线程试图进入睡眠时起作用。

所以我切换到 Monitor Wait&Pulse。然后我发现 Pulse 仅在我实际处于等待状态时才有效。所以现在我有一个看起来像这样的线程:

while (m_shouldRun)
{
    try
    {
        DoSomethingThatTakesSeveralSeconds();
        lock (this)
        {
            Monitor.Wait(this, 5000);
        }
    }
    catch (ThreadInterruptedException)
    {
        m_shouldRun = false;
    }
}

现在我需要制作我的停止功能。所以我开始了:

public void Stop()
{
    m_shouldRun = false;
    lock (this)
    {
        Monitor.Pulse(this);
    }
    thread.Join();
}

但这不起作用,因为我可能在线程工作时(当它不等待时)脉动。所以我添加了中断:

public void Stop()
{
    m_shouldRun = false;
    thread.Interrupt();
    lock (this)
    {
        Monitor.Pulse(this);
    }
    thread.Join();
}

另一种选择是使用:

public void Stop()
{
    m_shouldRun = false;
    while (!thread.Join(1000))
    {
        lock (this)
        {
            Monitor.Pulse(this);
        }
    }
}

问题

首选方法是什么?有没有第三种更好的方法?

【问题讨论】:

  • "起初我使用的是 Sleep 和 Interrupt。后来我发现 Interrupt 并没有真正中断 Sleep - 它只在线程尝试进入睡眠状态时才起作用。”这很奇怪。这是一个已知的错误?如果不是,您可以尝试用玩具示例重现它吗?

标签: c# multithreading


【解决方案1】:

优雅地停止线程的方法是让它自行完成。所以在 worker 方法中你可以有一个布尔变量来检查我们是否要中断。默认情况下,它将设置为false,当您从主线程将其设置为true 时,它会通过中断处理循环来停止扫描操作。

【讨论】:

  • +1 允许线程自行完成。任何其他方法都是混乱的。不要忘记用 volatile 关键字标记布尔标志。
  • 谢谢。你会注意到我确实有这样的标志。我不会在线程实际工作时中断它,但我确实想在它睡觉时中断它。如果它要睡 10 分钟,我不希望它继续睡。
  • 休眠 10 秒的线程对任何人都没有用。每当您需要执行某些任务但不要让它们处于休眠状态时,请使用 ThreadPool 绘制线程。让他们做有用的事情。
  • @Darin:这个线程应该定期扫描一个文件夹。它不是基于事件的。您如何建议我从 ThreadPool 中获取一个实例并每 10 分钟运行一次扫描?另外,这不是我的资源问题,因为我的应用程序中没有很多线程,例如这个线程。我想我可以使用计时器,但我不确定我是否喜欢这个主意。
  • 您如何建议我从 ThreadPool 中获取一个实例并每 10 分钟运行一次扫描:通过使用 ThreadPool.RegisterWaitForSingleObject 方法并将 executeOnlyOnce 参数设置为 false .当您解码停止时,只需使用 RegisteredWaitHandle.Unregister 取消注册等待句柄。
【解决方案2】:

另一种选择是使用事件:

private ManualResetEvent _event = new ManualResetEvent(false);


public void Run() 
{
 while (true)
 {
    DoSomethingThatTakesSeveralSeconds();
    if (_event.WaitOne(timeout))
      break;
 }
}

public void Stop() 
{
   _event.Set();
   thread.Join();
}

【讨论】:

  • 是的,这也可以。问题是什么是最好的方法。您的选项看起来确实比重复脉冲或中断+脉冲更好。
  • 好吧,我个人不会使用脉冲/中断。由于可能的 APC,Pulse 可能会出现问题(我相信线程可能不会注意到它)。中断名声不好:bluebytesoftware.com/blog/2007/08/23/…
  • 如果你只有一个线程在做这项工作,那就去做一个事件。否则使用 volatile 标志和超时唤醒并检查是否设置了标志(避免创建大量作为内核对象的事件)。
【解决方案3】:

我建议保持简单:

while (m_shouldRun)
{
    DoSomethingThatTakesSeveralSeconds();
    for (int i = 0; i < 5; i++)  // example: 5 seconds sleep
    {
        if (!m_shouldRun)
            break;
        Thread.Sleep(1000);
    }
}

public void Stop()
{
    m_shouldRun = false;
    // maybe thread.Join();
}

这样有以下优点:

  • 闻起来像是忙着等待,但事实并非如此。 $NUMBER_OF_SECONDS 检查是在等待阶段完成的,这与真正繁忙的等待中完成的数千次检查相比是无法比拟的。
  • 很简单,大大降低了多线程代码出错的风险。您的所有Stop 方法需要做的就是将m_shouldRun 设置为false 并(可能)调用Thread.Join(如果线程需要在Stop 离开之前完成)。不需要同步原语(除了将 m_shouldRun 标记为 volatile)。

【讨论】:

  • DoSomething 函数不会被强制中断。 Thread.Interrupt 仅在线程尝试阻塞时“发生”。请参阅 MS 文档(此处:msdn.microsoft.com/en-us/library/…)-“如果此线程当前未在等待、睡眠或加入状态下阻塞,则它将在下一次开始阻塞时被中断。”
  • @Eldad:好点,我把它和 Thread.Abort 搞混了。改变了我的答案。
【解决方案4】:

我想出了单独安排任务:

using System;
using System.Threading;

namespace ProjectEuler
{
    class Program
    {
        //const double cycleIntervalMilliseconds = 10 * 60 * 1000;
        const double cycleIntervalMilliseconds = 5 * 1000;
        static readonly System.Timers.Timer scanTimer =
            new System.Timers.Timer(cycleIntervalMilliseconds);
        static bool scanningEnabled = true;
        static readonly ManualResetEvent scanFinished =
            new ManualResetEvent(true);

        static void Main(string[] args)
        {
            scanTimer.Elapsed +=
                new System.Timers.ElapsedEventHandler(scanTimer_Elapsed);
            scanTimer.Enabled = true;

            Console.ReadLine();
            scanningEnabled = false;
            scanFinished.WaitOne();
        }

        static void  scanTimer_Elapsed(object sender,
            System.Timers.ElapsedEventArgs e)
        {
            scanFinished.Reset();
            scanTimer.Enabled = false;

            if (scanningEnabled)
            {
                try
                {
                    Console.WriteLine("Processing");
                    Thread.Sleep(5000);
                    Console.WriteLine("Finished");
                }
                finally
                {
                    scanTimer.Enabled = scanningEnabled;
                    scanFinished.Set();
                }
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多