【问题标题】:Gracefully shutdown a thread优雅地关闭一个线程
【发布时间】:2011-10-20 18:42:25
【问题描述】:

在我正在开发的应用程序中,我有一个简单的主窗体并显示日志数据,以及一个在循环中自主完成工作的工作线程。

MyWorker worker = new MyWorker();
MainForm mainForm = new MainForm();

// Subscribe form to log event so log data gets displayed
worker.Log += mainForm.Log;

// Start the worker thread's MainLoop
new Thread(new ThreadStart(worker.MainLoop)).Start();

// Show the form (blocking)
Application.Run(mainForm);

// If we end up here, the form has been closed and the worker has to stop running            
worker.Running = false;

如您所见,无论何时关闭表单,工作线程都应该停止。工人看起来像这样:

public class MyWorker
{
    public String Running { get; set; }

    public MyWorker()
    {
        Running = true;
    }

    public void MainLoop()
    {

        while (Running)
        {

            DoExtensiveWork1();
            if (!Running) return;

            DoExtensiveWork2();
            if (!Running) return;

            DoExtensiveWork3();
            if (!Running) return;

            DoExtensiveWork4();
            if (!Running) return;

            DoExtensiveWork5();         
            if (!Running) return;

            // We have to wait fifteen minutes (900 seconds) 
            // before another "run" can be processed
            for (int i = 0; i < 900; i++)
            {
                Thread.Sleep(1000);
                if (!Running) return;
            }
        }
    }
}

如您所见,我希望线程能够在连续工作操作之间切换时停止,而不是在操作中时停止。当操作 (DoExtensiveWorkN) 完成后,其状态和结果会在磁盘或数据库中持久保存,因此在操作正在进行时退出(例如,Thread.Abort)不是一种选择。

但是,我发现我刚刚编写的这段代码令人厌恶,尤其是“等待循环”,它会休眠一秒钟 900 次,以防止线程在检测到 Running 之前空闲 15 分钟。到false

我宁愿能够在主循环完成一项工作后立即抛出某种事件来停止主循环。

谁能指出正确的方向如何做到这一点,或者如果因为我完全误解了线程而需要完全重写,请告诉我解释这些原则的地方?

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    您可以大大整理各个任务的运行和 15 分钟的等待循环。

    我建议也许使用这样的东西:

    public class MyWorker
    {
        private readonly ManualResetEvent _stopEvent = new ManualResetEvent(false);
        private readonly Action[] _workUnits;
    
        private bool Running
        {
            get { return !_stopEvent.WaitOne(0); }
        }
    
        public MyWorker()
        {
            _workUnits = new Action[]
            {
                DoExtensiveWork1,
                DoExtensiveWork2,
                DoExtensiveWork3,
                DoExtensiveWork4,
                DoExtensiveWork5
            };
        }
    
        public void Stop()
        {
            _stopEvent.Set();
        }
    
        public void MainLoop()
        {
    
            while (Running)
            {
                foreach (var workUnit in _workUnits)
                {
                    workUnit();
                    if (!Running) return;
                }           
    
                // We have to wait fifteen minutes (900 seconds) 
                // before another "run" can be processed
                if (_stopEvent.WaitOne(900000)) return;
            }
        }
    }
    

    然后在下一个适当的点停止该过程:

    Worker.Stop();
    

    【讨论】:

      【解决方案2】:

      我建议使用System.Timers.Timer

      您可以使用正在运行的东西来完成您的工作,而不是使用睡眠,您只需将计时器设置为在 15 分钟内再次关闭。

      如果您想提前停止它,请调用某种中止方法(类似于设置 Running=true 变量)来停止计时器。

      需要注意的是,每次触发定时器事件都会启动一个新线程,因此您不必担心会杀死后台线程。您的线程完成其处理运行,将计时器设置为在 15 分钟内运行,然后线程自然完成。如果您在等待期间中止,那么您只需摆脱计时器并且不需要更多清理。如果您在运行期间中止,那么您让运行完成,最后它会检查一个标志并且不会再次启动计时器,然后线程就会结束。

      对于计时器,您需要将计时器设置为在流程结束时手动启动。另一种方法是让计时器每 15 分钟计时一次,但这意味着如果您的处理需要 10 分钟,那么下次运行前应该只有 5 分钟。如果花费超过 15 分钟,您可能会遇到麻烦。此外,手动重新启动计时器可确保在另一个正在运行时不会重新启动处理。

      【讨论】:

      • 计时器不能解决我遇到的主要问题:我不想在表单关闭时继续工作,我不想添加很多代码来保留检查。
      • @CodeCaster:啊,我误解了“所以在操作正在进行时退出(例如,通过 Thread.Abort)不是一个选项”意味着整个线程。我当时的想法是使用一系列动作,但铱星打败了我。不过,我仍然更喜欢等待 15 分钟的计时器。 :)
      • 我可以更清楚地描述这一点。 (还是)感谢你的建议。 :-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多