我有一个控件,其数据绑定到标准ObservableCollection
,我有一个后台任务调用服务来获取更多数据。
然后,我想在我的控件后面更新我的支持数据,同时显示“请稍候”对话框,但是当我将新项目添加到集合时,UI线程会在重新绑定并更新我的时候锁定控件。
我可以解决这个问题,以便我的动画和内容继续在我的“请稍候”对话框中运行吗?
或者至少给用户“外观”它没有锁定?
答案 0 :(得分:18)
如果我理解正确,您已经使用BackgroundWorker来检索数据,而只是将此数据分配给ObservableCollection就会锁定UI。
避免锁定UI的一种方法是通过排队多个调度程序方法将数据分配给较小的块中的ObservableCollection。在每个方法调用之间,可以处理UI事件。
以下会一次添加一个项目,这有点极端,但它说明了这个概念。
void UpdateItems()
{
//retrievedItems is the data you received from the service
foreach(object item in retrievedItems)
Dispatcher.BeginInvoke(DispatcherPriority.Background, new ParameterizedThreadStart(AddItem), item);
}
void AddItem(object item)
{
observableCollection.Add(item);
}
答案 1 :(得分:10)
ObservableCollection将引发CollectionChanged事件,这些事件将强制UI重新绑定数据,测量,排列和重绘。如果你有很多更新,这可能会花费很多时间。
通过在小包中分割作业,可以使用户认为UI处于活动状态。从UI线程使用Dispatcher(任何控件都引用它)来安排10-100个项目的集合更新操作(通过实验确定数量,这些只是为了支持这个想法)。
您的背景代码可能如下所示:
void WorkInBackground()
{
var results = new List<object>();
//get results...
// feed UI in packages no more than 100 items
while (results.Count > 0)
{
Application.Current.MainWindow.Dispatcher.BeginInvoke(
new Action<List<object>>(FeedUI),
DispatcherPriority.Background,
results.GetRange(0, Math.Min(results.Count, 100)));
results.RemoveRange(0, Math.Min(results.Count, 100));
}
}
void FeedUI(List<object> items)
{
// items.Count must be small enough to keep UI looks alive
foreach (var item in items)
{
MyCollection.Add(item);
}
}
答案 2 :(得分:1)
我有一个DLL,它运行一个工作线程并将事件发送回应用程序 - 在Windows窗体上完美运行,切换到WPF,一切都停止工作。我一直把头撞在砖墙上4个小时试图让它上班。但是,由于Microsoft的UI线程安全编组EnableCollectionSynchronization,我最终得到的解决方案提供了一个非常干净的实现来解决这个问题。
此Collection扩展了ObservableCollection并实现了EnableCollectionSynchronization,使这些对象可以在WPF和后台工作者之间使用。
编辑 :Microsoft's docs说出以下内容,所以我假设对象上下文共享并不重要。
context参数是一个任意对象,可用于启用集合同步时已知的信息。上下文可以是 null 。
<强> ThreadSafeCollection.cs 强>
using System.Collections.ObjectModel;
using System.Windows.Data;
namespace NSYourApplication
{
/// <summary>
/// This ObservableCollection is thread safe
/// You can update it from any thread and the changes will be safely
/// marshalled to the UI Thread WPF bindings
/// Thanks Microsoft!
/// </summary>
/// <typeparam name="T">Whatever type of collection you want!</typeparam>
public class ThreadSafeCollection<T> : ObservableCollection<T>
{
private static object __threadsafelock = new object();
public ThreadSafeCollection()
{
BindingOperations.EnableCollectionSynchronization(this, __threadsafelock);
}
}
}
示例WindowViewModel 的 WindowViewModel.cs 强>
namespace NSYourApplication
{
/// <summary>
/// Example View
/// BaseModelView implements "PropertyChanged" to update WPF automagically
/// </summary>
class TestViewModel : BaseModelView
{
public ThreadSafeCollection<string> StringCollection { get; set; }
/// <summary>
/// background thread implemented elsewhere...
/// but it calls this method eventually ;)
/// Depending on the complexity you might want to implement
/// [MethodImpl(MethodImplOptions.Synchronized)]
/// to Synchronize multiple threads to prevent chase-conditions,deadlocks etc
/// </summary>
public void NonUIThreadMethod()
{
// No dispatchers or invokes required here!
StringCollection.Add("Some Text from a background worker");
}
/// <summary>
/// Somewhere in the UIThread code it'll call this method
/// </summary>
public void UIThreadMethod()
{
StringCollection.Add("This text come from UI Thread");
}
/// <summary>
/// Constructor, creates a thread-safe collection
/// </summary>
public TestViewModel()
{
StringCollection = new ThreadSafeCollection<string>();
}
}
}
在xaml窗口/控件的列表框中使用 的 MainWindow.xaml 强>
<ListBox x:Name="wpfStringCollection" ItemsSource="{Binding StringCollection,Mode=OneWay}">
</ListBox>
答案 3 :(得分:0)
使用BackgroundWorker完成此任务。更新DoWork方法中的obsrvablecollection
答案 4 :(得分:-1)
使用此:
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(UpdateData), value);
private void UpdateData(int value)
{
BindingSourceProperty = value;
}