Threading and collections modification in WPF / C#

前端 未结 1 658
天涯浪人
天涯浪人 2020-12-04 00:44

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 th

相关标签:
1条回答
  • 2020-12-04 01:14

    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;
        });
    
    0 讨论(0)
提交回复
热议问题