【问题标题】:wpf app crashes when BackgroundWorker is used使用 BackgroundWorker 时 wpf 应用程序崩溃
【发布时间】:2010-10-06 22:10:42
【问题描述】:

我的视图模型在其构造函数中创建了一个 BackgroundWorker。 BackgroundWorker 从其 DoWork 事件处理程序更新模型的属性。下面的代码是一个人为的例子(ViewModelBase 几乎一字不差地取自 MVVM 论文)。


public class MyViewModel : ViewModelBase
{
    public int MyProperty
    {
        get
        {
            return this.my_property;
        }
        private set
        {
            if (this.my_property != value)
            {
                this.my_property = value;
                this.OnPropertyChanged("MyProperty");
            }
        }
    }

    public MyViewModel()
    {
        this.worker = new BackgroundWorker();
        this.worker.DoWork += (s, e) => { this.MyProperty = 1; };
        this.worker.RunWorkerAsync();
    }
}

我的视图绑定到这个视图模型:


public partial class MyPage : Page
{
    public MyPage()
    {
        InitializeComponent();
        this.DataContext = new MyViewModel();
        }
}

问题是我的应用程序在显示我的页面时会间歇性地崩溃。这肯定与 XAML 中的工作线程和数据绑定有关。似乎如果我从页面的 Loaded 事件处理程序内部启动工作程序,问题就会消失,但由于难以一致地重现,我不确定这是否是正确的解决方案。

有没有人有任何建议或想法可能是什么原因?

编辑:如果我在没有调试器的情况下运行它,我只能重现它。而系统日志中的错误是ShowDialog抛出的InvalidOperationException,帮助不大。在调试器下它运行良好。

感谢您的帮助 康斯坦丁

【问题讨论】:

    标签: wpf multithreading mvvm


    【解决方案1】:

    由于MyProperty 不是线程安全的,当两个线程尝试同时访问它时会发生奇怪的事情。你应该

    1. 使其线程安全(例如,通过使用lock 关键字)或

    2. 确保其值仅在 UI 线程中更改。

    我建议第二种方法,因为锁定总是一件棘手的事情。特别是 BackgroundWorker 已经支持在工作完成后将值传回 UI 线程:

    public void MyViewModel()
    {
        this.worker = new BackgroundWorker();
        this.worker.DoWork += (s, e) => { /* do something */ e.Result = 1; };
        this.worker.RunWorkerCompleted += (s, e) =>
        {
            // all of this happens back in the UI thread
            if (e.Error != null)
            {
                // do some error handling... some exception occurred during DoWork
            }
            else
            {
                this.MyProperty = (int)e.Result;
            }
        };
        this.worker.RunWorkerAsync();
    }
    

    【讨论】:

      【解决方案2】:

      听起来您的后台工作人员正在尝试从后台线程更新 XAML 标记/绑定。您必须调用 Dispatcher 以将值传回主线程,如下所示:

      在你的工作活动中:

      Dispatcher.BeginInvoke(delegate()
      {
      //UI element (test.Text) being updated with value i.ToString() from background thread
      test.Text += i.ToString();
      });
      }
      

      【讨论】:

      • 杰夫,感谢您的回复。它没有解释为什么它在大多数情况下都有效。另外,我的后台线程是我的视图模型的一部分,它与 UI 无关。
      • 我在帖子底部阅读了您的最后回复。如果您尝试从主线程外部更新 UI 元素,则必须使用调度程序 - ProgressChanged 位于主线程上,因为它旨在让工作人员向主线程报告已发生进度更改。它已经在主线程上,这就是它对您有用的原因。很高兴你让它工作。祝福,杰夫
      【解决方案3】:

      【讨论】:

        【解决方案4】:

        WPF 中不允许从后台线程对数据绑定对象触发 INotifyPropertyChanged.PropertyChangedINotifyCollectionChanged.CollectionChanged(通常来自 ObservableCollection)事件。

        您可以通过使用 Dispatcher.Invoke 设置属性或在从 DoWork 处理程序中完成的任何长时间运行的操作(网络或计算)返回值后在 RunWorkerCompleted 处理程序中设置属性来解决此问题。

        【讨论】:

        • 约翰,谢谢。我有一个工人填充的可观察集合(因此无法在 RunWorkerCompleted 上完成,因为我需要向用户显示部分填充的集合)。所以,在我的收藏中,我确实使用 Dispatcher.Invoke 来触发 CollectionChanged 事件。如果我不这样做,我的应用程序每次运行都会崩溃,而不仅仅是有时。
        【解决方案5】:

        我认为问题在于我正在从 DoWork 事件处理程序内部更新我的属性。在我将更新移动到 ProgressChanged 处理程序后,问题似乎已经消失了。我现在什至可以从 ProgressChanged 内部更新我的 ObservableCollection。所以看起来 ProgressChanged 事件处理程序是由框架自动在 UI 线程上调用的。

        【讨论】:

        • 你是对的。它基本上是在后台操作期间进行 UI 更新(如进度条)。
        猜你喜欢
        • 1970-01-01
        • 2011-01-01
        • 2018-09-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多