在工作线程上更新我的 ObservableCollection 仍然挂起我的 UI
Updating my ObservableCollection on a worker thread still hangs my UI
我的应用程序中有一个日志 window,当我有几千个日志时,过滤它们以包含或排除不同的日志级别会使 UI 在一段时间内没有响应工作负荷。所以我试图将繁重的工作转移到工作线程,但我仍然遇到同样的问题。
我正在使用 ObservableCollection
在我的模型中保存日志信息,我只是直接在我的 ViewModel 中引用它。我已经使用 BindingOperations.EnableCollectionSynchronization()
让我的工作线程更新我的可观察集合,而不将其分派给 UI 线程。
我 运行 使用以下
在工作线程上更新集合
Task.Run(new Action(() => FilterList()));
方法:
private void FilterList()
{
//Necessary even with EnableCollectionSynchronization
App.Current.Dispatcher.Invoke(new Action(() =>
{
FilteredLogEvents.Clear();
}));
foreach (LogEvent log in FilterLogEvents())
{
FilteredLogEvents.Add(log);
}
RaisePropertyChanged("FilteredLogEvents");
FinishedFilteringLogs();
}
//Filters and returns a list of filtered log events
private List<LogEvent> FilterLogEvents()
{
List<LogEvent> selectedEvents = (from x in LogEvents
where ((ViewDebugLogs == true) ? x.Level == "Debug" : false)
|| ((ViewErrorLogs == true) ? x.Level == "Error" : false)
|| ((ViewInfoLogs == true) ? x.Level == "Info" : false)
select x).ToList();
return selectedEvents;
}
这会导致 UI 在 foreach
上冻结。我也试过只更新一个 ObservableCollection
然后用 FilteredLogEvents = myNewCollection;
给它分配 FilteredLogEvents
这也会导致 UI 在那个过程中冻结一会儿。
如果我在 foreach
循环中使用 Thread.Sleep(1)
,UI 会保持响应,尽管这似乎是一个不优雅且老套的解决方案。
我需要做什么才能完成这项工作?
编辑:来自此 class (LogEntries
) 的更多代码上下文
FinishedFilteringLogsEventHandler
的回调返回到 ViewModel 以更改一个布尔值,在过滤完成时启用几个复选框。
//Constructor
public LogEntries()
{
foreach(NlogViewerTarget target in NLog.LogManager.Configuration.AllTargets.Where(t=>t is NlogViewerTarget).Cast<NlogViewerTarget>())
{
target.RecieveLog += RecieveLog;
}
FilteredLogEvents = new ObservableCollection<LogEvent>();
BindingOperations.EnableCollectionSynchronization(FilteredLogEvents, filteredLogEventsLock);
}
public delegate void FinishedFilteringLogsEvent();
public FinishedFilteringLogsEvent FinishedFilteringLogsEventHandler;
private object filteredLogEventsLock = new object();
public ObservableCollection<LogEvent> FilteredLogEvents { get; set; }
提高代码速度和响应能力的一些想法
几天前我问了一个类似的问题,有人建议我不要将线程池用于长时间的 运行ning 任务。线程池是可用线程的集合,与启动传统线程(如 System.ComponentModel.BackGroundWorker.
相比可以快速启动
虽然创建和启动一个真正的线程需要更多的时间,但这对于长 运行ning 任务来说没有问题。
线程池中的线程数量有限,所以最好不要将其用于较长的运行ning任务。
如果您 运行 一个任务,它只会在近期有线程可用时被安排到 运行。如果所有线程都忙,则线程启动前需要一些时间。
从 Task 到 Backgroundworker 的变化是有限的。如果你真的想坚持任务,考虑创建一个异步函数:
async void FilteredLogEvents.AddRangeAsync(IEnumerable<LogEvent> logEvents)
或者更好:
async void FilteredLogEvents.SetAsync(IEnumerable<LogEvent> logEvents)
在一个异步调用中进行清除和添加。
使您自己的函数异步:
private async void FilterList()
{
var filteredLogEvents = FilterLogEvents();
var myTask = Task.Run( () => FilteredLogEvents.SetAsync(filteredLogEvents);
// if desired do other things.
// wait until ready:
await myTask();
RaisePropertyChanged("FilteredLogEvents");
FinishedFilteringLogs();
}
顺便问一下:您确定在过滤时您的 logEvents 序列没有改变吗?
- 如果是这样,为什么使用 ToList() 而不是返回 IEnumerable 并使用延迟执行?
- 如果您不确定:如果在 FilterLogEvents 期间您的 logEvents 序列发生变化会怎样?
我的应用程序中有一个日志 window,当我有几千个日志时,过滤它们以包含或排除不同的日志级别会使 UI 在一段时间内没有响应工作负荷。所以我试图将繁重的工作转移到工作线程,但我仍然遇到同样的问题。
我正在使用 ObservableCollection
在我的模型中保存日志信息,我只是直接在我的 ViewModel 中引用它。我已经使用 BindingOperations.EnableCollectionSynchronization()
让我的工作线程更新我的可观察集合,而不将其分派给 UI 线程。
我 运行 使用以下
在工作线程上更新集合Task.Run(new Action(() => FilterList()));
方法:
private void FilterList()
{
//Necessary even with EnableCollectionSynchronization
App.Current.Dispatcher.Invoke(new Action(() =>
{
FilteredLogEvents.Clear();
}));
foreach (LogEvent log in FilterLogEvents())
{
FilteredLogEvents.Add(log);
}
RaisePropertyChanged("FilteredLogEvents");
FinishedFilteringLogs();
}
//Filters and returns a list of filtered log events
private List<LogEvent> FilterLogEvents()
{
List<LogEvent> selectedEvents = (from x in LogEvents
where ((ViewDebugLogs == true) ? x.Level == "Debug" : false)
|| ((ViewErrorLogs == true) ? x.Level == "Error" : false)
|| ((ViewInfoLogs == true) ? x.Level == "Info" : false)
select x).ToList();
return selectedEvents;
}
这会导致 UI 在 foreach
上冻结。我也试过只更新一个 ObservableCollection
然后用 FilteredLogEvents = myNewCollection;
给它分配 FilteredLogEvents
这也会导致 UI 在那个过程中冻结一会儿。
如果我在 foreach
循环中使用 Thread.Sleep(1)
,UI 会保持响应,尽管这似乎是一个不优雅且老套的解决方案。
我需要做什么才能完成这项工作?
编辑:来自此 class (LogEntries
) 的更多代码上下文
FinishedFilteringLogsEventHandler
的回调返回到 ViewModel 以更改一个布尔值,在过滤完成时启用几个复选框。
//Constructor
public LogEntries()
{
foreach(NlogViewerTarget target in NLog.LogManager.Configuration.AllTargets.Where(t=>t is NlogViewerTarget).Cast<NlogViewerTarget>())
{
target.RecieveLog += RecieveLog;
}
FilteredLogEvents = new ObservableCollection<LogEvent>();
BindingOperations.EnableCollectionSynchronization(FilteredLogEvents, filteredLogEventsLock);
}
public delegate void FinishedFilteringLogsEvent();
public FinishedFilteringLogsEvent FinishedFilteringLogsEventHandler;
private object filteredLogEventsLock = new object();
public ObservableCollection<LogEvent> FilteredLogEvents { get; set; }
提高代码速度和响应能力的一些想法
几天前我问了一个类似的问题,有人建议我不要将线程池用于长时间的 运行ning 任务。线程池是可用线程的集合,与启动传统线程(如 System.ComponentModel.BackGroundWorker.
相比可以快速启动虽然创建和启动一个真正的线程需要更多的时间,但这对于长 运行ning 任务来说没有问题。
线程池中的线程数量有限,所以最好不要将其用于较长的运行ning任务。
如果您 运行 一个任务,它只会在近期有线程可用时被安排到 运行。如果所有线程都忙,则线程启动前需要一些时间。
从 Task 到 Backgroundworker 的变化是有限的。如果你真的想坚持任务,考虑创建一个异步函数:
async void FilteredLogEvents.AddRangeAsync(IEnumerable<LogEvent> logEvents)
或者更好:
async void FilteredLogEvents.SetAsync(IEnumerable<LogEvent> logEvents)
在一个异步调用中进行清除和添加。
使您自己的函数异步:
private async void FilterList()
{
var filteredLogEvents = FilterLogEvents();
var myTask = Task.Run( () => FilteredLogEvents.SetAsync(filteredLogEvents);
// if desired do other things.
// wait until ready:
await myTask();
RaisePropertyChanged("FilteredLogEvents");
FinishedFilteringLogs();
}
顺便问一下:您确定在过滤时您的 logEvents 序列没有改变吗?
- 如果是这样,为什么使用 ToList() 而不是返回 IEnumerable 并使用延迟执行?
- 如果您不确定:如果在 FilterLogEvents 期间您的 logEvents 序列发生变化会怎样?