.net 我从哪里获得线程安全的 CollectionView?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2137769/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 13:50:35  来源:igfitidea点击:

Where do I get a thread-safe CollectionView?

.netwpfmultithreadingthread-safetycollectionview

提问by Jonathan Allen

When updating a collection of business objects on a background thread I get this error message:

在后台线程上更新业务对象集合时,我收到以下错误消息:

This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.

这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。

Ok, that makes sense. But it also begs the question, what version of CollectionView does support multiple threads and how do I make my objects use it?

好吧,这是有道理的。但这也引出了一个问题,CollectionView 的哪个版本支持多线程以及如何让我的对象使用它?

采纳答案by Nathan Phillips

The following is an improvement on the implementation found by Jonathan. Firstly it runs each event handler on the dispatcher associated with it rather than assuming that they are all on the same (UI) dispatcher. Secondly it uses BeginInvoke to allow processing to continue while we wait for the dispatcher to become available. This makes the solution much faster in situations where the background thread is doing lots of updates with processing between each one. Perhaps more importantly it overcomes problems caused by blocking while waiting for the Invoke (deadlocks can occur for example when using WCF with ConcurrencyMode.Single).

以下是对 Jonathan 发现的实现的改进。首先,它在与其关联的调度程序上运行每个事件处理程序,而不是假设它们都在同一个 (UI) 调度程序上。其次,它使用 BeginInvoke 来允许在我们等待调度程序可用时继续处理。在后台线程进行大量更新并在每个线程之间进行处理的情况下,这使得解决方案更快。也许更重要的是,它克服了在等待 Invoke 时阻塞导致的问题(例如,在将 WCF 与 ConcurrencyMode.Single 一起使用时可能会发生死锁)。

public class MTObservableCollection<T> : ObservableCollection<T>
{
    public override event NotifyCollectionChangedEventHandler CollectionChanged;
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
        if (CollectionChanged != null)
            foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
            {
                DispatcherObject dispObj = nh.Target as DispatcherObject;
                if (dispObj != null)
                {
                    Dispatcher dispatcher = dispObj.Dispatcher;
                    if (dispatcher != null && !dispatcher.CheckAccess())
                    {
                        dispatcher.BeginInvoke(
                            (Action)(() => nh.Invoke(this,
                                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                            DispatcherPriority.DataBind);
                        continue;
                    }
                }
                nh.Invoke(this, e);
            }
    }
}

Because we are using BeginInvoke, it is possible that the change being notified is undone before the handler is called. This would typically result in an "Index was out of range." exception being thrown when the event arguments are checked against the new (altered) state of the list. In order to avoid this, all delayed events are replaced with Reset events. This could cause excessive redrawing in some cases.

因为我们使用的是 BeginInvoke,所以有可能在调用处理程序之前撤消通知的更改。这通常会导致“索引超出范围”。根据列表的新(更改)状态检查事件参数时抛出异常。为了避免这种情况,所有延迟事件都替换为复位事件。在某些情况下,这可能会导致过度重绘。

回答by luke

Use:

用:

System.Windows.Application.Current.Dispatcher.Invoke(
    System.Windows.Threading.DispatcherPriority.Normal,
    (Action)delegate() 
    {
         // Your Action Code
    });

回答by Cameron MacFarland

Thispost by Bea Stollnitz explains thaterror message and why it's worded the way it is.

Bea Stollnitz 的这篇文章解释了错误消息以及它为何如此措辞。

EDIT:From Bea's blog

编辑:来自 Bea 的博客

Unfortunately, this code results in an exception: “NotSupportedException – This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.” I understand this error message leads people to think that, if the CollectionView they're using doesn't support cross-thread changes, then they have to find the one that does. Well, this error message is a little misleading: none of the CollectionViews we provide out of the box supports cross-thread collection changes. And no, unfortunately we can not fix the error message at this point, we are very much locked down.

不幸的是,此代码导致异常:“NotSupportedException – 这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。” 我明白这个错误信息让人们认为,如果他们使用的 CollectionView 不支持跨线程更改,那么他们必须找到支持的那个。好吧,这个错误信息有点误导:我们提供的所有 CollectionViews 都不支持跨线程集合更改。不,不幸的是,我们此时无法修复错误消息,我们非常锁定。

回答by Jonathan Allen

Found one.

找到了一个。

public class MTObservableCollection<T> : ObservableCollection<T>
{
   public override event NotifyCollectionChangedEventHandler CollectionChanged;
   protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      var eh = CollectionChanged;
      if (eh != null)
      {
         Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                 let dpo = nh.Target as DispatcherObject
                 where dpo != null
                 select dpo.Dispatcher).FirstOrDefault();

        if (dispatcher != null && dispatcher.CheckAccess() == false)
        {
           dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
        }
        else
        {
           foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
              nh.Invoke(this, e);
        }
     }
  }
}

http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx

http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx

回答by Richard

You can also look at: BindingOperations.EnableCollectionSynchronization.

你也可以看看:BindingOperations.EnableCollectionSynchronization

See Upgrading to .NET 4.5: An ItemsControl is inconsistent with its items source

请参阅升级到 .NET 4.5:ItemsControl 与其项目源不一致

回答by norekhov

Sorry, can't add a comment but all this is wrong.

抱歉,无法添加评论,但这一切都是错误的。

ObservableCollection is not thread safe. Not only because of this dispatcher issues, but it's not thread safe at all (from msdn):

ObservableCollection 不是线程安全的。不仅因为这个调度程序问题,而且它根本不是线程安全的(来自 msdn):

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

此类型的任何公共静态(在 Visual Basic 中为共享)成员都是线程安全的。不保证任何实例成员都是线程安全的。

Look here http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx

看这里 http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx

There's also a problem when calling BeginInvoke with a "Reset" action. "Reset" is the only action where handler should look at the collection itself. If you BeginInvoke a "Reset" and then immediately BeginInvoke a couple of "Add" actions than handler will accept a "Reset" with already updated collection and next "Add"'s will create a mess.

使用“重置”操作调用 BeginInvoke 时也存在问题。“重置”是处理程序应该查看集合本身的唯一操作。如果您开始调用“重置”,然后立即开始调用几个“添加”操作,则处理程序将接受带有已更新集合的“重置”,接下来的“添加”将造成混乱。

Here's my implementation which works. Actually I'm thinking of removing BeginInvoke at all:

这是我的实现。实际上,我正在考虑完全删除 BeginInvoke:

Fast performing and thread safe observable collection

快速执行和线程安全的可观察集合

回答by Hamish

You can get wpf to manage cross thread changes to a collection by enabling collection synchronization like so:

您可以通过启用集合同步来让 wpf 管理对集合的跨线程更改,如下所示:

BindingOperations.EnableCollectionSynchronization(collection, syncLock);
listBox.ItemsSource = collection;

This tells WPF that the collection may be modified off the UI thread so it knows it has to marshal any UI changes back to the appropriate thread.

这告诉 WPF 可以在 UI 线程之外修改集合,因此它知道它必须将任何 UI 更改封送回适当的线程。

There is also an overload to provide a synchronization callback if you don't have a lock object.

如果您没有锁定对象,还有一个重载来提供同步回调。

回答by Nalan Madheswaran

Try This:

尝试这个:

this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() =>
{

 //Code

}));

回答by Developer

If you want to update WPF UI Control periodically and at the same time use UI you can use DispatcherTimer.

如果您想定期更新 WPF UI 控件并同时使用 UI,您可以使用DispatcherTimer

XAML

XAML

<Grid>
        <DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Name="dgDownloads" VerticalAlignment="Top" Width="548" />
        <Label Content="" Height="28" HorizontalAlignment="Left" Margin="0,221,0,0" Name="lblFileCouner" VerticalAlignment="Top" Width="173" />
</Grid>

C#

C#

 public partial class DownloadStats : Window
    {
        private MainWindow _parent;

        DispatcherTimer timer = new DispatcherTimer();

        ObservableCollection<FileView> fileViewList = new ObservableCollection<FileView>();

        public DownloadStats(MainWindow parent)
        {
            InitializeComponent();

            _parent = parent;
            Owner = parent;

            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            dgDownloads.ItemsSource = null;
            fileViewList.Clear();

            if (_parent.contentManagerWorkArea.Count > 0)
            {
                foreach (var item in _parent.contentManagerWorkArea)
                {
                    FileView nf = item.Value.FileView;

                    fileViewList.Add(nf);
                }
            }

            if (fileViewList.Count > 0)
            {
                lblFileCouner.Content = fileViewList.Count;
                dgDownloads.ItemsSource = fileViewList;
            }
        }   

    }

回答by Ana Betts

None of them, just use Dispatcher.BeginInvoke

都没有,只需使用 Dispatcher.BeginInvoke