【问题标题】:WPF ObservableList Dispatcher threadWPF ObservableList 调度程序线程
【发布时间】:2013-02-07 23:30:51
【问题描述】:

我正在将一个项目从 Windows 窗体转换为 WPF 格式。目前,我已将所有数据绑定到元素。我现在在ObservableCollection 提出一个问题,说:

这种类型的 CollectionView 不支持对其进行更改 SourceCollection 来自与 Dispatcher 线程不同的线程。

如何使我的代码线程安全?或者我如何引导对 Dispatcher 线程的更改?我看过一些关于它的帖子,但我对如何在我自己的项目中应用它感到困惑。也许有人可以为我解释一下?

这是我的ObservableList.cs代码:

public class ObservableList<T> : ObservableCollection<T>
{
    #region Private members

    bool isInAddRange = false;

    #endregion Private members

    #region Public methods

    /// <summary>
    /// Creates a new empty ObservableList of the provided type. 
    /// </summary>
    public ObservableList()
    {

    }

    /// <summary>
    /// Handles the event when a collection has changed.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        // intercept this when it gets called inside the AddRange method.
        if (!isInAddRange)
            base.OnCollectionChanged(e);
    }

    /// <summary>
    /// Adds a collection of items to the ObservableList.
    /// </summary>
    /// <param name="items"></param>
    public void AddRange(IEnumerable<T> items)
    {
        isInAddRange = true;
        foreach (T item in items)
        {
            Add(item);
        }

        isInAddRange = false;

        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
        base.OnCollectionChanged(e);

    }

    #endregion Public methods
}

}

编辑:在 ywm 给出答案后,我将 AddRange 课程更改为:

public void AddRange(IEnumerable<T> items)
{
    isInAddRange = true;
    foreach (T item in items)
    {
        if (item != null)
        {
            Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
                {
                    Add(item);
                }));
        }
    }

    isInAddRange = false;

    var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
    base.OnCollectionChanged(e);

}

现在,我所有的 ObservableList 都是 null

【问题讨论】:

    标签: c# .net wpf multithreading dispatcher


    【解决方案1】:

    当您将项目添加到 ObservableCollection 时,您需要调用 UI 调度程序线程来执行此操作。

    这样做是这样的:

      Dispatcher.CurrentDispatcher.Invoke(() =>
      {
            foreach (var myModel in itemsToAdd)
            {
                        Images.Add(mymodel);                   
            } 
      });
    

    然后在类中使用它,

        public ObservableList<String> Strings { get; set; }
    
        public MyViewModel()
        {
            this.Strings = new ObservableList<string>();
    
            this.Strings.AddRange(new[] { "1", "2", "3", "4" });
        }
    

    【讨论】:

    • 我还会通过推荐一种专用方法来扩展您的解决方案,该方法可以添加项目并同步线程,例如 AddItemThreadSafe(myItem item),这样当您从多个来源添加项目时,您不会得到多余的代码。您还可以考虑创建自己的 ThreadSafeObservableCollection 来包装 normale 可观察集合
    • 好的,所以我调用了 UI 线程。我必须先将 lambda 表达式转换为 Action-type。当我尝试运行它时,我获得了很多空指针异常。至于 Boas Enkler 的评论,我喜欢创建一个额外的类来处理安全线程的想法。虽然,我不知道从哪里开始,也不知道为什么给定的解决方案不起作用......
    • 我仔细研究了一下,似乎得到了一个 ObservableList 返回null。这意味着添加项目还没有完全奏效。让我更新我的问题以反映我自己所做的更改
    • 我已经更新了我的答案并测试了你的代码,添加字符串时它似乎可以工作。
    • 我已经更新了我的代码以反映你的代码,但我仍然得到一个空指针异常。创建的 ObservableList 是null,所以我不能在其中添加项目。如果不使用来自 Dispatch 的 Invoke,情况并非如此(但几秒钟后,由于我的问题中所述的问题,应用程序崩溃了)。
    【解决方案2】:

    当您现在在 UI 线程中调用 Add 方法时,您仍在调用线程中的 AddRange 方法中引发事件。因此,您最终会遇到与进行更改之前相同的问题。

    试试这个:

    public void AddRange(IEnumerable<T> items)
    {
        isInAddRange = true;
        foreach (T item in items)
        {
            if (item != null)
            {
                Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
                    {
                        Add(item);
                    }));
            }
        }
    
        isInAddRange = false;
    
        var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
        Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
        {
            base.OnCollectionChanged(e);
        });
    
    }
    

    【讨论】:

    • 问题是集合不是由 UI 线程创建的,而是由另一个线程创建的。有关它的更多信息,请参阅我的回复!感谢您的时间和帮助!
    【解决方案3】:

    我找到了一个干净的解决方案here

    这里可能出现的问题不是应该调用 UI 线程来进行更改,而是创建 Collection 的线程!这不一定是 UI 线程!

    因此,我将代码更改为以下内容:

    public class ObservableList<T> : ObservableCollection<T>
    {
        #region Private members
    
        bool isInAddRange = false;
        private readonly Dispatcher _currentDispatcher;
    
        #endregion Private members
    
        #region Public methods
    
        /// <summary>
        /// Creates a new empty ObservableList of the provided type. 
        /// </summary>
        public ObservableList()
        {
            //Assign the current Dispatcher (owner of the collection) 
            _currentDispatcher = Dispatcher.CurrentDispatcher;
        }
    
        /// <summary>
        /// Executes this action in the right thread
        /// </summary>
        ///<param name="action">The action which should be executed</param>
        private void DoDispatchedAction(Action action)
        {
            if (_currentDispatcher.CheckAccess())
                action.Invoke();
            else
                _currentDispatcher.Invoke(DispatcherPriority.DataBind, action);
        }
    
        /// <summary>
        /// Handles the event when a collection has changed.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            // intercept this when it gets called inside the AddRange method.
            if (!isInAddRange)
            {
                DoDispatchedAction(() => base.OnCollectionChanged(e));
            }
        }
    
        /// <summary>
        /// Adds a collection of items to the ObservableList.
        /// </summary>
        /// <param name="items"></param>
        public void AddRange(IEnumerable<T> items)
        {
            isInAddRange = true;
            foreach (T item in items)
            {
                if (item != null)
                    {
                        Add(item);
                }
            }       
    
            isInAddRange = false;
    
            var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,items.ToList());
            DoDispatchedAction(() => base.OnCollectionChanged(e));
    
        }
    
        #endregion Public methods
    }
    

    我承认我还没有测试过AddRange() 方法本身。链接网站上给出的示例是:

    /// <summary>
    /// Inserts a item at the specified index
    /// </summary>
    ///<param name="index">The index where the item should be inserted</param>
    ///<param name="item">The item which should be inserted</param>
    protected override void InsertItem(int index, T item)
    {
        DoDispatchedAction(() => base.InsertItem(index, item));
    }
    

    感谢大家对我的帮助!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-23
      • 1970-01-01
      • 1970-01-01
      • 2018-02-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多