0

我目前正在为一个项目使用 .NET 3.5,我需要实现一个静态队列,以获取它的项目由一个后台线程入队并由另一个后台线程出队。由于 .NET 3.5 中没有 ObservableQueue 或类似的东西,我尝试派生我自己的队列并实现 INotifyCollectionChanged,因为我的 UI 应该向用户显示队列的内容。

但是当我尝试运行它时,第一个后台工作人员将一个项目排入队列,CollectionChanged 被引发,然后我最终得到一个异常,例如

c# 调用线程无法访问此对象,因为不同的线程拥有它

我的业务对象(队列中的那些东西)都实现了 INotifyPropertyChanged,并且使它们出队的后台工作人员也更改了一些属性,因此同一个线程也调用 PropertyChanged 和 CollectionChanged。奇怪的是,当 PropertyChanged 被提升时我没有收到任何错误,但是 CollectionChanged 崩溃了......有人可以帮我解决这个问题吗?

4

5 回答 5

0

首先,我应该从一开始就扩展 ObservableCollection 以实现 INotifyCollectionChanged 集合。对于多线程集合,请参阅此类,这可能对您有所帮助(仅适用于多线程,如果您想添加需要实现它的队列行为)

公共类 ThreadSafeObservableCollection : ObservableCollection { 私有 SynchronizationContext SynchronizationContext;

    public ThreadSafeObservableCollection()
    {
        SynchronizationContext = SynchronizationContext.Current;

        // current synchronization context will be null if we're not in UI Thread
        if (SynchronizationContext == null)
            throw new InvalidOperationException("This collection must be instantiated from UI Thread, if not, you have to pass SynchronizationContext to con                                structor.");
    }

    public ThreadSafeObservableCollection(SynchronizationContext synchronizationContext)
    {
        if (synchronizationContext == null)
            throw new ArgumentNullException("synchronizationContext");

        this.SynchronizationContext = synchronizationContext;
    }

    protected override void ClearItems()
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.ClearItems()), null);
    }

    protected override void InsertItem(int index, T item)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.InsertItem(index, item)), null);
    }

    protected override void RemoveItem(int index)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.RemoveItem(index)), null);
    }

    protected override void SetItem(int index, T item)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.SetItem(index, item)), null);
    }

    protected override void MoveItem(int oldIndex, int newIndex)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.MoveItem(oldIndex, newIndex)), null);
    }
}
于 2012-10-03T19:28:28.877 回答
0

使用这样的东西:

if (System.Windows.Application.Current.Dispatcher.CheckAccess())
        {
            Messages.Add(message);
        }
        else
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal,
                new Action(() => Messages.Add(message)));
        }

您只能从 GUI-Thread 访问,因为 GUI 可能绑定到集合。并且许多使用 gui 的线程并不是一件好事,因为结果你会经常收到错误。

所以想想:为什么你不能做这样的事情:listBox1.Items.Add(...) 从另一个线程。现在您知道为什么不能从另一个线程访问列表了。

(如果你真的不知道为什么你可以为它做类似 listBox1.Items.Add(...) google ......你会发现很多文章

于 2012-08-27T14:02:30.613 回答
0

当您尝试从非调度程序(即非 GUI)线程与 GUI 交互时,您会遇到这样的异常。在您的情况下,我假设您只是在引发 CollectionChanged 事件而不检查它是否是 GUI 线程。为了解决这个问题,正如 chrisendymion 所写,我们应该Dispatcher. 我在这里看到两个可能的选择:

1) 对您的自定义队列(添加/删除)的每次调用都是在 Dispatcher ( Dispatcher.Invoke)上进行的

2) 将引发 CollectionChanged 事件的代码包装到相同的Dispatcher.Invoke.

希望这可以帮助。

于 2012-08-27T13:03:47.760 回答
0

当您的后台线程修改队列时,您是否尝试过委托?

喜欢 :

if (Dispatcher.Thread != Thread.CurrentThread)
{
       Dispatcher.Invoke(new Action(delegate()
       {
           //Modify your collection
       }));
 }
于 2012-08-27T12:41:00.120 回答
0

对于为什么这不起作用,每个人都有一个很好的技术答案。分配给当前线程的“Dispatcher”对象无权访问 UI。

在 .NET 中,如果当前线程中不存在调度程序,.NET 将新建另一个调度程序,但是这将具有与您的后台线程相同的同步上下文 - 而不是它需要同步访问的 UI 线程。

对我来说最简单的解决方案是在 ObservableCollection 中保存对原始调度程序的引用,并在不同的调度程序中覆盖基类调用,这样的事情应该可以工作:

public class ObservableDispatcherCollection<T> : ObservableCollection<T> where T : class
    {
        private Dispatcher _dispatcher;

        public ObservableDispatcherCollection(Dispatcher dispatcher)
        {
            _dispatcher = dispatcher; 
        }

        public ObservableDispatcherCollection(Control parent)
        {
            _dispatcher = parent.Dispatcher;
        }

        protected override void     OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            _dispatcher.Invoke(new Action(() =>
            {
                base.OnCollectionChanged(e);
            }));
        }

        protected override void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            _dispatcher.Invoke(new Action(() =>
            {
                base.OnPropertyChanged(e);
            }));
        }
}
于 2013-10-15T08:30:44.193 回答