HY,
我有一个Observable Collection,它与列表框绑定。我将日志添加到Observable Collection。我总是立即将消息添加到Observable Collecten。但是这个列表只在循环结束时才更新,但我想在for循环中添加一个项目时更新它。这就是我使用Thread但我遇到一些问题的原因。
我有一个线程安全的ObservableCollection:
class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
if (dispatcherObject != null)
{
Dispatcher dispatcher = dispatcherObject.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => handler.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
handler.Invoke(this, e);
}
}
}
这是我的测试类:
public partial class MainWindow : Window
{
ThreadSafeObservableCollection<Animal> list = new ThreadSafeObservableCollection<Animal>();
public MainWindow()
{
InitializeComponent();
list.Add(new Animal() { Name = "test1" });
list.Add(new Animal() { Name = "test2" });
this.DataContext = list;
}
private void dsofsdkfd(object sender, RoutedEventArgs e)
{
//Version 1
Task.Factory.StartNew(() => test());
//Version2
/*
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var token = Task.Factory.CancellationToken;
Task.Factory.StartNew(() => test(), token, TaskCreationOptions.None, uiScheduler);
*/
}
public void test()
{
for (int i = 0; i < 10000; i++)
{
list.Add(new Animal() { Name = "test" + i });
System.Threading.Thread.Sleep(1);
}
}
}
查看评论 Version1 的 private void dsofsdkfd(object sender,RoutedEventArgs e)功能。 一开始它可以工作,所以每次添加项目时列表都会更新。在几个条目之后我得到一个例外:
“开发人员的信息”(使用Text Visualizer阅读 这个):\ r \ n这个异常被抛出,因为生成器用于控制 'System.Windows.Controls.ListBox Items.Count:1089',名称为'Logger' 收到了不同意的CollectionChanged事件序列 与Items集合的当前状态。下列 检测到差异:\ r \ n累计计数994不同 从实际计数1089. [累计计数是(最后重置计数+
添加 - 自上次重置后#Removes。)\ r \ n \ r \ n以下一个或多个来源可能引发了错误的事件:\ r \ n
System.Windows.Controls.ItemContainerGenerator \ r \ n
System.Windows.Controls.ItemCollection \ r \ n
System.Windows.Data.ListCollectionView \ r \ n *
WpfApplication1.ThreadSafeObservableCollection`1 [WpfApplication1.Animal, WpfApplication1,Version = 1.0.0.0,Culture = neutral, PublicKeyToken = null]] \ r \ n(已加星标的来源被认为更多 可能是导致问题的原因。)\ r \ n \ r \ n最常见的原因 是(a)更改集合或其计数而不提高 相应的事件,以及(b)提出索引不正确的事件 或项目参数。\ r \ n \ r \ n例外的堆栈跟踪描述了如何 检测到不一致,而不是它们是如何发生的。得到一个 更及时的例外,设置附加属性 生成器上的'PresentationTraceSources.TraceLevel'值为'High' 并重新运行该方案。一种方法是运行命令 类似如下:\ n
System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator, System.Diagnostics.PresentationTraceLevel.High)\ r \ n来自Immediate 窗口。这导致检测逻辑在每次运行之后运行 CollectionChanged事件,因此它会降低应用程序的速度。\ r \ n“
请参阅版本2 的 private void dsofsdkfd(对象发件人,RoutedEventArgs e)功能。 我还使用FromCurrentSynchronizationContext与TaskScheduler一起尝试了它。
然后它不会抛出任何异常,但是我在开始时遇到了同样的问题,所以只有当for循环结束时,列表框才会刷新。
如何在添加元素时更新列表框?
祝你好运
答案 0 :(得分:1)
我不会为此推出自己的ObservableCollection。我只是在UI线程上执行.Add调用。
public void test()
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal {Name = "test" + i};
// invoke list.Add on the UI thread
this.Dispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
请注意,由于您位于Window子类中,this.Dispatcher
将对应于UI线程的调度程序。如果将此逻辑移动到模型或视图模型类,则需要在UI线程上显式捕获Dispatcher.Current
的值,并将该调度程序手动传递给后台线程。
编辑:OP要求提供有关在FrameworkElement类之外使用Dispatcher的更多信息。这是你如何做到这一点。通过调用Dispatcher.CurrentDispatcher
在UI线程上获取UI线程的调度程序。然后,该调度程序直接传递给后台线程程序。
public class MainWindowViewModel
{
// this should be called on the UI thread
public void Start()
{
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
// start the background thread and pass it the UI thread dispatcher
Task.Factory.StartNew(() => BackgroundThreadProc(uiDispatcher));
}
// this is called on the background thread
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal { Name = "test" + i };
// invoke list.Add on the UI thread
uiDispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
}
答案 1 :(得分:1)
您需要维护当前的调度程序线程。您必须仅在当前调度程序线程中更新集合。一种方法是使用调度程序类的BiginInvoke()方法。
将当前调度程序保存在构造函数中的变量中,然后在需要时使用它。
_currentDispatcher = Application.Current.Dispatcher;
例如:我们有一个场景,如果出现错误,我们会弹出一个错误窗口。如果错误计数为零,我们需要关闭错误窗口。现在,如果我们在另一个线程(而不是UI线程)中处理事件和消息,那么我们需要保存UI线程调度程序对象,并且需要使用它来更新集合或任何其他操作。我在这里关闭错误窗口。 (我没有准备好更新收集的解决方案。)
if (ErrorNotifications.Count == 0)
_currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ErrorNotificationWindow>(CloseErrorNotificationWindow), _errWindow);
这里的CloseErrorNotificationWindow是带参数_errWindow的方法。
private void CloseErrorNotificationWindow(ErrorNotificationWindow _errWindow)
{
if (_errWindow == null)
return;
if (_errWindow.IsActive)
_errWindow.Close();
}
在CloseErrorNotificationWindow()方法中,您可以更新集合,它不应该提供任何异常,因为您将使用主UI线程来执行此操作。 希望这会有所帮助。