C# 如何通过工作线程更新 ObservableCollection?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2091988/
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
How do I update an ObservableCollection via a worker thread?
提问by Maciek
I've got an ObservableCollection<A> a_collection;
The collection contains 'n' items. Each item A looks like this:
我有一个ObservableCollection<A> a_collection;
包含“n”个项目的集合。每个项目 A 看起来像这样:
public class A : INotifyPropertyChanged
{
public ObservableCollection<B> b_subcollection;
Thread m_worker;
}
Basically, it's all wired up to a WPF listview + a details view control which shows the b_subcollection
of the selected item in a separate listview (2-way bindings, updates on propertychanged etc.).
基本上,它全部连接到 WPF 列表视图 + 详细信息视图控件,该控件b_subcollection
在单独的列表视图(2 向绑定、属性更改更新等)中显示所选项目的 。
The problem showed up for me when I started to implement threading. The entire idea was to have the whole a_collection
use it's worker thread to "do work" and then update their respective b_subcollections
and have the gui show the results in real time.
当我开始实施线程时,问题就出现在我身上。整个想法是让整个a_collection
使用它的工作线程“做工作”,然后更新它们各自的b_subcollections
并让 gui 实时显示结果。
When I tried it , I got an exception saying that only the Dispatcher thread can modify an ObservableCollection, and work came to a halt.
当我尝试它时,我得到一个异常,说只有 Dispatcher 线程可以修改 ObservableCollection,并且工作停止了。
Can anyone explain the problem, and how to get around it?
谁能解释这个问题,以及如何解决它?
采纳答案by Josh
Technically the problem is not that you are updating the ObservableCollection from a background thread. The problem is that when you do so, the collection raises its CollectionChanged event on the same thread that caused the change - which means controls are being updated from a background thread.
从技术上讲,问题不在于您正在从后台线程更新 ObservableCollection。问题是,当您这样做时,集合会在导致更改的同一线程上引发其 CollectionChanged 事件 - 这意味着正在从后台线程更新控件。
In order to populate a collection from a background thread while controls are bound to it, you'd probably have to create your own collection type from scratch in order to address this. There is a simpler option that may work out for you though.
为了在控件绑定到它时从后台线程填充集合,您可能必须从头开始创建自己的集合类型以解决此问题。不过,有一个更简单的选项可能适合您。
Post the Add calls onto the UI thread.
将 Add 调用发布到 UI 线程上。
public static void AddOnUI<T>(this ICollection<T> collection, T item) {
Action<T> addMethod = collection.Add;
Application.Current.Dispatcher.BeginInvoke( addMethod, item );
}
...
b_subcollection.AddOnUI(new B());
This method will return immediately (before the item is actually added to the collection) then on the UI thread, the item will be added to the collection and everyone should be happy.
此方法将立即返回(在项目实际添加到集合之前)然后在 UI 线程上,项目将被添加到集合中,每个人都应该很高兴。
The reality, however, is that this solution will likely bog down under heavy load because of all the cross-thread activity. A more efficient solution would batch up a bunch of items and post them to the UI thread periodically so that you're not calling across threads for each item.
然而,现实情况是,由于所有跨线程活动,此解决方案可能会在重负载下陷入困境。更有效的解决方案是将一堆项目批处理并定期将它们发布到 UI 线程,这样您就不会为每个项目跨线程调用。
The BackgroundWorkerclass implements a pattern that allows you to report progress via its ReportProgressmethod during a background operation. The progress is reported on the UI thread via the ProgressChanged event. This may be another option for you.
该BackgroundWorker的类实现的模式,使您可以通过汇报其进展ReportProgress后台操作过程中的方法。通过 ProgressChanged 事件在 UI 线程上报告进度。这可能是您的另一种选择。
回答by Jon
New option for .NET 4.5
.NET 4.5 的新选项
Starting from .NET 4.5 there is a built-in mechanism to automatically synchronize access to the collection and dispatch CollectionChanged
events to the UI thread. To enable this feature you need to call BindingOperations.EnableCollectionSynchronization
from within your UI thread.
从 .NET 4.5 开始,有一个内置机制可以自动同步对集合的访问并将CollectionChanged
事件分派到 UI 线程。要启用此功能,您需要从 UI 线程中调用。BindingOperations.EnableCollectionSynchronization
EnableCollectionSynchronization
does two things:
EnableCollectionSynchronization
做两件事:
- Remembers the thread from which it is called and causes the data binding pipeline to marshal
CollectionChanged
events on that thread. - Acquires a lock on the collection until the marshalled event has been handled, so that the event handlers running UI thread will not attempt to read the collection while it's being modified from a background thread.
- 记住调用它的线程并导致数据绑定管道
CollectionChanged
在该线程上封送事件。 - 在处理完编组事件之前获取集合上的锁定,以便运行 UI 线程的事件处理程序不会在从后台线程修改集合时尝试读取该集合。
Very importantly, this does not take care of everything: to ensure thread-safe access to an inherently not thread-safe collection you have to cooperatewith the framework by acquiring the same lock from your background threads when the collection is about to be modified.
非常重要的是,这并不能解决所有问题:为了确保对本质上不是线程安全的集合的线程安全访问,您必须在集合即将被修改时通过从后台线程获取相同的锁来与框架合作。
Therefore the steps required for correct operation are:
因此,正确操作所需的步骤是:
1. Decide what kind of locking you will be using
1. 决定您将使用哪种锁定方式
This will determine which overload of EnableCollectionSynchronization
must be used. Most of the time a simple lock
statement will suffice so this overloadis the standard choice, but if you are using some fancy synchronization mechanism there is also support for custom locks.
这将决定EnableCollectionSynchronization
必须使用哪个重载。大多数情况下,一个简单的lock
语句就足够了,因此这种重载是标准选择,但如果您使用一些奇特的同步机制,也可以支持自定义锁。
2. Create the collection and enable synchronization
2. 创建集合并启用同步
Depending on the chosen lock mechanism, call the appropriate overload on the UI thread. If using a standard lock
statement you need to provide the lock object as an argument. If using custom synchronization you need to provide a CollectionSynchronizationCallback
delegate and a context object (which can be null
). When invoked, this delegate must acquire your custom lock, invoke the Action
passed to it and release the lock before returning.
根据选择的锁定机制,在 UI 线程上调用适当的重载。如果使用标准lock
语句,您需要提供锁定对象作为参数。如果使用自定义同步,您需要提供一个CollectionSynchronizationCallback
委托和一个上下文对象(可以是null
)。调用时,此委托必须获取您的自定义锁,调用Action
传递给它的锁并在返回之前释放锁。
3. Cooperate by locking the collection before modifying it
3.通过在修改之前锁定集合进行协作
You must also lock the collection using the same mechanism when you are about to modify it yourself; do this with lock()
on the same lock object passed to EnableCollectionSynchronization
in the simple scenario, or with the same custom sync mechanism in the custom scenario.
当您要自己修改集合时,您还必须使用相同的机制锁定它;在简单场景中lock()
使用相同的锁对象执行此操作EnableCollectionSynchronization
,或者在自定义场景中使用相同的自定义同步机制。
回答by WhileTrueSleep
With .NET 4.0 you can use these one-liners:
在 .NET 4.0 中,您可以使用这些单行代码:
.Add
.Add
Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));
.Remove
.Remove
Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));
回答by LadderLogic
Collection synchronization code for posterity. This uses simple lock mechanism to enable collection sync. Notice that you'll have to enable collection sync on the UI thread.
子孙后代的集合同步代码。这使用简单的锁定机制来启用集合同步。请注意,您必须在 UI 线程上启用集合同步。
public class MainVm
{
private ObservableCollection<MiniVm> _collectionOfObjects;
private readonly object _collectionOfObjectsSync = new object();
public MainVm()
{
_collectionOfObjects = new ObservableCollection<MiniVm>();
// Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{ BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); }));
}
/// <summary>
/// A different thread can access the collection through this method
/// </summary>
/// <param name="newMiniVm">The new mini vm to add to observable collection</param>
private void AddMiniVm(MiniVm newMiniVm)
{
lock (_collectionOfObjectsSync)
{
_collectionOfObjects.Insert(0, newMiniVm);
}
}
}