为什么关闭应用程序时线程不加入?

时间:2019-02-08 14:39:38

标签: c# wpf multithreading

我制作了带有进度条的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();
        }
    }

2 个答案:

答案 0 :(得分:1)

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

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

第二个是您致电Dispatcher.Invoke,而不是Dispatcher.BeginInvokeInvoke会将消息发布到调度程序的消息队列中,并等待,直到该消息被处理,从而阻止了调用线程。

那么会发生什么:

  1. 调度程序线程将running设置为false
  2. 调度程序线程阻塞,等待后台线程退出
  3. 后台线程完成其睡眠,将running读为true,将消息发布到调度程序线程的消息队列中,并阻止等待它被处理
  4. 该消息将永远不会被处理,因为调度程序线程被阻塞,等待后台线程退出
  5. 后台线程将永远不会退出,因为它被阻止,等待分派器线程处理该消息
  6. 僵局

之所以会这样,是因为您违反了许多不同的基本规则:

  1. 不要阻塞UI线程。曾经。
  2. 从没有锁(或其他同步)的两个线程访问共享状态。曾经。
  3. 如果您确实有充分理由,请仅使用通过BeginInvoke调用。

正如评论所说,为此使用DispatcherTimer。

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

答案 1 :(得分:0)

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

我建议您避免所有线程并在一起使用Microsoft的Reactive Framework(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();
    }
}

这使事情变得非常简单。