3

我目前正在使用 C#/WPF 开发一个系统,该系统访问 SQL 数据库,检索一些数据(大约 10000 个项目),然后应该更新一组数据点,这些数据点用作我在我的应用程序中使用的 WPF 图表的数据(Visifire 图表解决方案,以防有人想知道)。

当我编写直截了当的单线程解决方案时,如您所料,系统会在应用程序查询数据库、检索数据和呈现图表所花费的时间段内挂起。但是,我想通过在使用多线程获取和处理数据时向用户添加等待动画来加快此任务。但是,会出现两个问题:

  1. 在使用多线程时,我无法更新我的集合并保持它们同步。我不是很熟悉Dispatcher门课不是很熟悉,所以我不太确定该怎么做。
  2. 由于我显然没有很好地处理多线程,等待动画不会出现(因为 UI 被冻结)。

我试图弄清楚是否有一种有效地将多线程用于集合的好方法。我发现微软有线程安全的集合,但似乎没有一个符合我的需要。

另外,如果有人有很好的参考来学习和理解Dispatcher我将不胜感激。

编辑:这是我正在尝试做的代码片段,也许它可以更清楚地说明我的问题:

private List<DataPoint> InitializeDataSeries(RecentlyPrintedItemViewModel item)
{
    var localDataPoints = new List<DataPoint>();

    // Stopping condition for recursion - if we've hit a childless (roll) item
    if (item.Children.Count == 0)
    {
        // Populate DataPoints and return it as one DataSeries
        _dataPoints.AddRange(InitializeDataPoints(item));
    }
    else
    {
        // Iterate through all children and activate this function on them (recursion)
        var datapointsCollection = new List<DataPoint>();
        Parallel.ForEach(item.Children, child => datapointsCollection = (InitializeDataSeries((RecentlyPrintedItemViewModel)child)));

        foreach (var child in item.Children)
        {    
            localDataPoints.AddRange(InitializeDataSeries((RecentlyPrintedItemViewModel)child));
        }
    }

    RaisePropertyChanged("DataPoints");
    AreDataPointsInitialized = true;

    return localDataPoints;
}

谢谢

4

1 回答 1

3

Dispatcher是一个对象,用于管理单个线程上的多个工作项队列,每个队列在何时执行其工作项时具有不同的优先级。

通常引用 WPF的Dispatcher主应用程序线程,并用于在不同的DispatcherPriorities调度代码,以便它们以特定的顺序运行。

例如,假设您要显示加载图形,加载一些数据,然后隐藏该图形。

IsLoading = true;
LoadData();
IsLoading = false;

如果您一次完成所有这些操作,它将锁定您的应用程序,您将永远看不到加载图形。这是因为所有代码都默认在DispatcherPriority.Normal队列中运行,所以当它完成运行时,加载图形将再次隐藏。

相反,您可以使用Dispatcher来加载数据并以低于 的调度程序优先级隐藏图形DispatcherPriority.Render,例如DispatcherPriority.Background,以便其他队列中的所有任务在加载发生之前完成,包括渲染加载图形。

IsLoading = true;

Dispatcher.BeginInvoke(DispatcherPriority.Background,
    new Action(delegate() { 
        LoadData();
        IsLoading = false;
     }));

但这仍然不理想,因为Dispatcher引用了应用程序的单个 UI 线程,因此在长时间运行的进程发生时,您仍将锁定线程。

更好的解决方案是为您的长时间运行的进程使用单独的线程。我个人的偏好是使用任务并行库,因为它简单易用。

IsLoading = true;
Task.Factory.StartNew(() => 
    {
        LoadData();
        IsLoading = false;
    });

但这仍然会给您带来问题,因为 WPF 对象只能从创建它们的线程中修改。

因此,如果您ObservableCollection<DataItem>在后台线程上创建一个,则无法从代码中除该后台线程之外的任何位置修改该集合。

典型的解决方案是在后台线程上获取数据并将其返回到临时变量中的主线程,并让主 UI 线程创建对象并用从后台线程获取的数据填充它。

所以通常你的代码最终看起来像这样:

IsLoading = true;

Task.Factory.StartNew(() => 
    {
        // run long process and return results in temp variable
        return LoadData();
    })
    .ContinueWith((t) => 
    {
        // this block runs once the background code finishes

        // update with results from temp variable
        UpdateData(t.Result)

        // reset loading flag
        IsLoading = false;
    });
于 2013-04-10T15:58:50.877 回答