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

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

问题描述

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



当我写直接的单线程解决方案时,期望,挂起在应用程序查询数据库,检索数据和呈现图表所花费的时间段。但是,我想通过向用户添加等待动画来更快地完成此任务,同时使用多线程提取和处理数据。但是,会出现两个问题:


  1. 我在更新我的集合时遇到问题,并在使用多线程时保持同步。我不太熟悉 Dispatcher 类,所以我不知道该怎么做。

  2. 显然不能很好地处理多线程,等待动画将不会显示(因为UI被冻结)。

试图找出是否有一个很好的方法来有效地使用多线程的集合。我发现微软有线程安全收藏,但没有一个似乎适合我



此外,如果任何人有一个很好的参考来学习和理解 Dispatcher ,我会非常感谢。



编辑:
这里是我想要做的一段代码,也许它可以解释我的问题:

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

//停止递归的条件 - 如果我们打了一个无子的(roll)项目
if(item.Children.Count == 0)
{
//填充DataPoints并将其作为一个DataSeries返回
_dataPoints.AddRange(InitializeDataPoints(item));
}
else
{
//迭代所有子项并在它们上激活此函数(递归)
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 通常引用WPF的主应用程序线程,用于在不同的 DispatcherPriorities ,以便以特定顺序运行。



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

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

如果一次性执行此操作,则会锁定您的应用程序,加载图形。这是因为默认情况下所有代码在 DispatcherPriority.Normal 队列中运行,所以在完成运行时,加载图形将再次隐藏。

$ b您可以使用 Dispatcher 加载数据并以比 DispatcherPriority更低的调度器优先级隐藏图形.Render ,例如 DispatcherPriority.Background ,因此其他队列中的所有任务在加载发生之前完成,包括渲染加载图形。 / p>

  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)=>
{
//这个块在后台代码完成后运行

//更新结果临时变量
UpdateData(t.Result)

//重置加载标志
IsLoading = false;
});


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. 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).

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.

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

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

Thanks

解决方案

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.

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;

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.

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

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

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

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.

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天全站免登陆