Jonathan Perry
Jonathan Perry

Reputation: 3053

Threading and collections modification in WPF / C#

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

Upvotes: 3

Views: 1071

Answers (1)

Rachel
Rachel

Reputation: 132548

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

Upvotes: 3

Related Questions