WPF/C#中的线程和集合修改 [英] Threading and collections modification in WPF / C#

查看:37
本文介绍了WPF/C#中的线程和集合修改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm currently developing a system in C# / WPF which accesses an SQL database, retrieves some data (around 10000 items) and then should update a collection of data points that is used as data for a WPF chart I'm using in my application (Visifire charting solution, in case anyone was wondering).

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

When I wrote the straight-forward, single-threaded solution, the system would, as you might expect, hang for the period of time it took the application to query the database, retrieve the data and render the charts. However, I wanted to make this task quicker by adding a wait animation to the user while the data was being fetched and processed using multithreading. However, two problems arise:

  1. 我在使用多线程时无法更新我的集合并保持它们同步.我对 Dispatcher 类不是很熟悉,所以我不太确定该怎么做.
  2. 由于我显然没有很好地处理多线程,所以不会显示等待动画(因为 UI 被冻结).
  1. I'm having trouble updating my collections and keeping them synchronized when using multithreading. I'm not very familiar with the Dispatcher class, so I'm not very sure what to do.
  2. Since I'm obviously not handling the multi-threading very well, the wait animation won't show up (since the UI is frozen).

我想弄清楚是否有一种好方法可以有效地将多线程用于集合.我发现 Microsoft 有 线程安全集合,但似乎没有一个适合我需要.

I'm trying to figure out if there's a good way to use multi-threading effectively for collections. I found that Microsoft had Thread-Safe collections but none seems to fit my needs.

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

Also, if anyone have a good reference to learn and understand the Dispatcher I would highly appreciate it.

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

Here's a code snippet of what I'm trying to do, maybe it can shed some more light on my question:

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;
}

谢谢

推荐答案

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

The Dispatcher is an object used to manage multiple queues of work items on a single thread, and each queues has a different priority for when it should execute it's work items.

Dispatcher 通常引用 WPF 的主应用程序线程,用于在不同的DispatcherPriorities 以便它们按特定顺序运行.

The Dispatcher usually references WPF's main application thread, and is used to schedule code at different DispatcherPriorities so they run in a specific order.

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

For example, suppose you want to show a loading graphic, load some data, then hide the graphic.

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

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

If you do this all at once, it will lock up your application and you won't ever see the loading graphic. This is because all the code runs by default in the DispatcherPriority.Normal queue, so by the time it's finished running the loading graphic will be hidden again.

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

Instead, you could use the Dispatcher to load the data and hide the graphic at a lower dispatcher priority than DispatcherPriority.Render, such as DispatcherPriority.Background, so all tasks in the other queues get completed before the loading occurs, including rendering the loading graphic.

IsLoading = true;

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

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

But this still isn't ideal because the Dispatcher references the single UI thread of the application, so you will still be locking up the thread while your long running process occurs.

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

A better solution is to use a separate thread for your long running process. My personal preference is to use the Task Parallel Library because it's simple and easy to use.

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

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

But this can still give you problems because WPF objects can only be modified from the thread that created them.

因此,如果您在后台线程上创建 ObservableCollection,则不能从该后台线程以外的代码中的任何位置修改该集合.

So if you create an ObservableCollection<DataItem> on a background thread, you cannot modify that collection from anywhere in your code other than that background thread.

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

The typical solution is to obtain your data on a background thread and return it to the main thread in a temp variable, and have the main UI thread create the object and fill it with data obtained from the background thread.

通常你的代码看起来像这样:

So often your code ends up looking something like this :

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;
    });

这篇关于WPF/C#中的线程和集合修改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆