【问题标题】:How do I properly close a C# application that has created multiple threads?如何正确关闭已创建多个线程的 C# 应用程序?
【发布时间】:2011-04-26 02:00:55
【问题描述】:

我正在编写一个 GUI 应用程序。

应用程序在其生命周期内打开多个线程。其中一个线程正在处理可能来自其他应用程序的事件,因此它在 while(true) 循环中等待从未终止的事件。

用户可以随时关闭应用程序。我想关闭主应用程序打开的所有线程。

我正在使用 Process.GetCurrentProcess().Kill();暂时解决这个问题。

这是一个好的解决方案吗?如果不是,为什么以及处理这个问题的正确方法是什么,如何关闭主应用程序打开的所有线程?

【问题讨论】:

  • 你应该避免while(true)的空闲工作。至少添加一个Sleep(100) 给你的cpu 一些喘息的空间。查看诸如albahari.com/threadingcsharpcourse.com 之类的免费书籍,了解如何在不进行空闲工作的情况下正确地保持线程处于活动状态。
  • while(true) 循环正在等待事件。由于它有一个阻塞的waitOne(),它并没有粗暴地使用我的cpu。

标签: c# .net winforms multithreading


【解决方案1】:

如果您将新线程创建为后台线程(通过在启动它们之前设置IsBackground),它们将在主线程(应用程序线程)终止时自动停止。

(来自 MSDN):

线程要么是后台线程,要么是前台线程。后台线程与前台线程相同,只是后台线程不会阻止进程终止。一旦属于一个进程的所有前台线程都已终止,公共语言运行时就会结束该进程。任何剩余的后台线程都将停止并且不会完成。

【讨论】:

  • 设置IsBackground = true 听起来可能适合您的情况。当我有一个线程持续阻塞Console.ReadLine 并等待输入时,我不得不使用该属性。 但是, 在大多数情况下,您应该尝试将程序设计为在计算或处理线程完成之前不允许关闭,或者您可以在计算或处理代码中构建取消机制.
  • 我同意@jnylen,你希望有某种机制让你的线程优雅地退出,而不仅仅是在某些操作的中间关闭。
  • @jnylen:是的,如果线程正在执行需要干净终止的操作(例如写入文件),您需要通过发出信号停止然后发出干净的终止信号等待。如果线程可以被中断,将它们设置为后台线程就足够了。
【解决方案2】:

一旦您已经有线程在等待某些事件,只需再添加一个事件,当触发时将指示线程终止。

如果您不需要为其他线程提供一些优雅关闭的方法,您可以将它们切换到“后台线程”模式以确保自动终止 - see MSDN 彻底讨论这个话题。

【讨论】:

    【解决方案3】:

    有很多方法可以解决这个问题,但理想情况下,您希望线程自行正常退出,而不是仅仅终止进程。

    你可以像这样做一些非常简单的事情:

    public class ThreadSignal
    {
       public bool Stop { get; set; }
    }
    

    然后在你的线程循环中,做:

    public void DoWork(object state) 
    {
       ThreadSignal signal = (ThreadSignal)state;
       while(!signal.Stop)
       {
          // Do work here    
       }
    }
    

    然后,当您准备停止时,将您的 ThreadSignal.Stop 设置为 true。这是一个非常简单的示例,但它为您提供了一个起点。

    【讨论】:

      【解决方案4】:

      您应该在循环中等待 ManualResetEvent(或 AutoResetEvent)。 然后在关闭时将成员变量设置为 true:

      public class MyForm : Form
      {
          private AutoResetEvent _workTrigger = new AutoResetEvent();
          private bool _shuttingDown = false;
          private Thread _thread;
      
          public void Form_Initialize()
          {
              _thread = new Thread(MyThreadMethod);
              _thread.Start();
          }
      
          public static void MyThreadMethod(object State)
          {
              while (!_shuttingDown)
              {
                  //wait for jobs.
                  _workTrigger.WaitOne(); //can add a timeout as parameter.
      
                  //do some work here
      
              }
      
          }
      
      
          public void Form_Closing(object source, EventArgs e)
          {
             _shuttingDown = true;
             _workTrigger.Set();
      
             //wait for it to exit. You could use the timeout
             //parameter and a loop to not block the UI
             _thread.Join();  
          }
      }
      

      【讨论】:

        【解决方案5】:

        正如您提到的,它是一个 GUI 应用程序,因此负责消息循环的主线程负责提醒用户想要退出程序的无限 (while(true)) 循环。我建议将 true 替换为另一个 boolean 以表示用户已关闭窗口,如下所示:while(windowIsOpen) 并在卸载表单时将其设置为 false。

        【讨论】:

          【解决方案6】:

          不要在应用程序周围丢失线程 - 将它们保留在某个地方(List<Thread> 会很好)。然后在适当的时候(关闭时间)通知每个人它应该完成它正在做的事情并退出。

          然后,.Join()所有这些,然后允许应用程序退出。

          永远不要去 'ThreadAbort' 领域,它是潜伏在那里的力量的阴暗面。

          【讨论】:

            【解决方案7】:

            一般来说我是这样的:

            • 创建一个封装此行为的类(例如,在后台处理传入的消息
            • 让类继承自 IDisposable。当调用 Dispose() 时,设置一个名为 _disposed 的私有变量
            • 在我的 Class 构造函数中创建我的专用线程。
            • 有一个名为 _workToDo 的私有 AutoResetEvent。您的后台线程将等待此事件,并且仅在此事件发出信号时执行工作循环。
            • 有一个公共方法将消息发送到后台工作人员,该工作人员将工作排队,然后设置 _workToDo 以告诉后台线程完成工作。

            把这些放在一起,你会得到:

            public class BackgroundProcessor : IDisposed
            {
              private Thread _backgroundThread;
              private bool _disposed;
              private AutoResetEvent _workToDo = new AutoResetEvent(false);
              // where T is a class with the set of parameters for your background work
              private Queue<T> _workQueue = Queue.Synchronized(new Queue<T>);
            
              public BackgroundProcessor()
              {
                _backgroundThread = new Thread(DoBackgroundWork);
                _backgroundThread.Start();
              }
            
              public void Dispose()
              {
                _disposed = true;
            
                // Wait 5 seconds for the processing of any previously submitted work to finish.
                // This gives you a clean exit.  May want to check return value for timeout and log
                // a warning if pending background work was not completed in time.
                // If you're not sure what you want to do yet, a Debug.Assert is a great place to
                // start because it will let you know if you do or don't go over time in general
                // in your debug builds.
                // Do *not* Join() and wait infinitely.  This is a great way to introduce shutdown
                // hangs into your app where your UI disappears but your process hangs around
                // invisibly forever.  Nasty problem to debug later...
                Debug.Assert(_backgroundThread.Join(5000)); 
              }
            
              // Called by your 'other application'
              public void GiveMeWorkToDo(T workParameters)
              {
                _workQueue.Enqueue(workParameters);
                _workToDo.Set();
              }
            
              private void DoBackgroundWork()
              {
                while (!_disposed)
                {
                  // 500 ms timeout to WaitOne allows your Dispose event to be detected if there is
                  // No work being submitted.  This is a fancier version of a Thread.Sleep(500)
                  // loop.  This is better because you will immediately start work when a new
                  // message is posted instead of waiting for the current Sleep statement to time
                  // out first.
                  _workToDo.WaitOne(500);
            
                  // It's possible multiple sets of work accumulated or that the previous loop picked up the work and there's none left.  This is a thread safe way of handling this.
                  T workParamters = _workQueue.Count > 0 ? workParameters = _workQueue.Dequeue() : null;
                  do
                  {
                    DoSomething(workParameters);
            
                    workParameters = _workQueue.Count > 0 ? workParameters = _workQueue.Dequeue() : null;
                  } while (workParameters != null)
                }
              }
            }
            

            【讨论】:

              【解决方案8】:

              考虑使用BackGroundWorker 类。由于它使用线程池(通过 BeginInvoke()),因此您将获得后台线程。作为奖励,您可以获得方便的进度报告、取消和完成回调(已经编组到 UI 线程)。

              【讨论】:

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