优雅地关闭线程

7
在我正在开发的应用程序中,我有一个主窗体,它只是坐在那里显示日志数据,并有一个工作线程自主循环运行。
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次,每次1秒钟,以防止线程在检测到Running已设置为false之前闲置15分钟。
我更希望能够抛出某种事件,在完成一项工作后尽快停止主循环。
有人可以指点我如何做到这一点吗?或者如果需要完全重写,因为我完全误解了线程,请告诉我一些解释这些原则的地方?
2个回答

14

您可以显著简化每个任务的运行和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();

0

我建议使用System.Timers.Timer

你可以使用正在运行的东西来完成你的工作,而不是使用sleep,你只需要设置计时器在15分钟后再次触发即可。

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

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

对于计时器,您需要在处理过程结束时手动设置计时器开始。另一种选择是每15分钟让计时器滴答一次,但这意味着如果您的处理只花费了10分钟,那么下一次运行前只有5分钟。如果处理时间超过15分钟,可能会出现问题。此外,手动重新启动计时器可以确保在另一个处理正在运行时不会重新启动处理。


定时器不会解决我遇到的主要问题:我不希望在表单关闭时继续执行工作,并且我不想添加大量代码来不断检查这一点。 - CodeCaster
@CodeCaster:啊,我误解了“因此,在操作正在进行时退出(例如,Thread.Abort)不是一个选项”的意思,认为整个线程都会退出。那么我的想法就是使用一个Action数组,但Iridium已经超越了我。尽管如此,我仍然更喜欢定时器等待15分钟。 :) - Chris
我本可以更清晰地描述那个问题。不过还是谢谢你的建议。 :-) - CodeCaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接