【问题标题】:Why the thread doesn't joins when closing application?为什么关闭应用程序时线程不加入?
【发布时间】:2019-07-02 19:58:05
【问题描述】:

我制作了一个带有进度条的 WPF 应用程序。进度条的值在函数“Background_Work”的“while”循环中更新为随机值。此函数在单独的线程中运行。 如果我关闭窗口,我想结束函数(通过结束“while”循环)并加入线程。

问题是,在某些情况下,窗口会冻结并且不会关闭。

    public partial class MainWindow : Window
    {
        Random random;
        Thread background_work;
        bool running;

        public MainWindow()
        {
            InitializeComponent();

            random = new Random();
            background_work = new Thread(Background_Work);

            running = true;
            background_work.IsBackground = true;
            background_work.Start();
        }

        private void Background_Work()
        {
            while (running)
            {
                myBar.Dispatcher.Invoke(() => { myBar.Value = random.Next(0, 100); });
                Thread.Sleep(1000);
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            running = false;
            background_work.Join();
        }
    }

【问题讨论】:

  • 使用 Dispatcher.Invoke 和 Thread.Sleep 的线程绝对是错误的方法。请改用 DispatcherTimer。
  • ^^ 这里是文档的链接:DispatchTimer
  • 感谢您的提示

标签: c# wpf multithreading


【解决方案1】:

我认为正在发生的事情是两件事的结合,它们共同导致了僵局。

首先是running 字段从多个线程访问,但不同步。如果您从多个线程访问数据,它必须被锁定。在这种情况下,主线程将该字段设置为false后,后台线程可能会在一段时间内将其读取为true。

第二个是你打电话给Dispatcher.Invoke,而不是Dispatcher.BeginInvokeInvoke 将向调度程序的消息队列发布一条消息,并等待直到该消息被处理,从而阻塞调用线程。

那么会发生什么:

  1. 调度程序线程将running 设置为false
  2. 调度线程阻塞,等待后台线程退出
  3. 后台线程完成睡眠,错误地将running 读取为true,将消息发送到调度程序线程的消息队列,并阻塞等待处理
  4. 消息永远不会被处理,因为调度线程被阻塞等待后台线程退出
  5. 后台线程永远不会退出,因为它在等待调度程序线程处理消息时被阻塞
  6. 死锁

发生这种情况是因为您违反了许多不同的基本规则:

  1. 永远不要阻塞 UI 线程。永远。
  2. 永远不要在没有锁(或其他同步)的情况下从两个线程访问共享状态。永远。
  3. 只有在确实有充分理由的情况下才使用 Invoke 而不是 BeginInvoke。

正如 cmets 所说,为此使用 DispatcherTimer。

如果您确实需要取消后台线程,请使用CancellationToken

【讨论】:

    【解决方案2】:

    调用background_work.Join() 会阻塞当前线程,同时等待background_work 线程自然完成。您在 while 循环中忙碌,因此它永远不会结束,因此您的 UI 将永远等待。

    我建议您避免使用所有线程并使用 Microsoft 的响应式框架(NuGet System.Reactive.Windows.Threading 用于 WPF 位)。

    那么你可以这样做:

    public partial class MainWindow : Window
    {
        Random random;
        IDisposable subscription;
    
        public MainWindow()
        {
            InitializeComponent();
    
            random = new Random();
    
            subscription =
                Observable
                    .Interval(TimeSpan.FromSeconds(1.0))
                    .ObserveOnDispatcher()
                    .Subscribe(_ => myBar.Value = random.Next(0, 100));
        }
    
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            subscription.Dispose();
        }
    }
    

    这让事情变得非常简单。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-07
      • 1970-01-01
      • 1970-01-01
      • 2011-02-20
      • 1970-01-01
      • 1970-01-01
      • 2013-09-18
      相关资源
      最近更新 更多