在工作线程上更新我的ObservableCollection仍会挂起我的UI

时间:2015-10-30 07:37:28

标签: c# multithreading ui-thread

我的应用程序中有一个日志窗口,当我有几千个日志时,过滤它们以包含或排除不同的日志级别会使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,然后使用FilteredLogEventsFilteredLogEvents = myNewCollection;分配Thread.Sleep(1),这也会导致UI在此过程中暂停一段时间。

如果我在foreach循环中使用LogEntries,UI仍会保持响应,尽管这似乎是一个不优雅和hacky的解决方案。

我需要做些什么来完成这项工作?

编辑:此类中的更多代码上下文(FinishedFilteringLogsEventHandler //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; } 的回调返回到ViewModel,以更改bool,在过滤完成时启用两个复选框。

UIPanGentureRecognizer

1 个答案:

答案 0 :(得分:1)

要考虑提高代码的速度和响应能力的一些想法

前几天我问了一个类似的问题,有人建议我不要使用线程池来完成长时间运行的任务。线程池是可用线程的集合,与启动System.ComponentModel.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序列发生变化会发生什么?