Snake Eyes
Snake Eyes

Reputation: 16764

Process items from datagrid WPF one by one when use BackgroundWorker

I want to process items one by one, every item from a datagrid element.

I have a method which creates a background worker:

internal void Run(Action doWork, Action completed, Action loadingBeforeAction = null, Action loadingAfterAction = null)
{
    using (BackgroundWorker worker = new BackgroundWorker())
    {
        worker.DoWork += (s, ev) =>
        {
            if (loadingBeforeAction != null)
            {
                _dispatcher.Invoke(loadingBeforeAction);
            }

            doWork();
        };

        worker.RunWorkerCompleted += (s, ev) =>
        {
            if (loadingAfterAction != null)
            {
                _dispatcher.Invoke(loadingAfterAction);
            }

            completed();
        };

        worker.RunWorkerAsync();
    }
}

And now process the selected items from datagrid:

var folders = btn.Name.Equals("test") 
    ? _model.Folders.ToArray()
    : fileDataGrid.SelectedItems.Cast<FolderStatus>().ToArray();"

and

foreach (var folder in folders)
{
    Run(() =>
    {
        Dispatcher.Invoke(()=> {_model.Message = $"This is message for {folder.Name}"});
        // long operation here
    }, () =>
    {
        // stuff
    }, () =>
     {
         _model.IsBusy = true;
     }, () =>
     {
         _model.IsBusy = false;
     });
}

Seems that all items are processed simultaneously and process message flick from text to other depends on _model.Message text.

How to process item one by one but without blocking the UI?

Upvotes: 1

Views: 118

Answers (1)

dymanoid
dymanoid

Reputation: 15197

BackgroundWorker is a legacy component from Windows Forms. You shouldn't use it with WPF applications. It will work, but there are better ways to implement what you want.

The easiest way is to use the TAP (Task-based Asynchronous Programming) pattern, supported by the async and await C# keywords.

If you're targeting at least the .NET Framework 4.5 or .NET Core, you have everything out-of-the box. For .NET Framework 4.0, there are NuGet packages to enable this functionality.

The implementation could look like this:

async void MyButtonClick(object sender, RoutedEventArgs e)
{
    var btn = (Button)sender;

    var folders = btn.Name.Equals("test") 
        ? _model.Folders.ToArray()
        : fileDataGrid.SelectedItems.Cast<FolderStatus>().ToArray();"

    foreach (var folder in folders)
    {
        // This will be executed on the UI thread
        _model.Message = $"This is message for {folder.Name}"};
        _model.IsBusy = true;

        // This will be executed on a worker thread due to Task.Run
        await Task.Run(() => YourLongRunningOperation(folder));

        await Task.Run(() => SomeOtherLongRunningOperation());

        // This will be executed again on the UI thread
        _model.IsBusy = false;
    }
}

A couple of notes:

  • The method above has a signature async void (object, EventArgs). However, async methods should return a Task or a Task<T>. async void is here to make the event handler async. This is the only case when you should use async void - UI event handlers. Note that an uncaught exception in an async void UI event handler will crash your application.
  • When await Task.Run() returns, the thread context will be restored to the original one. So, your _model.IsBusy = false code will be executed on the UI thread again. If you don't need this, use await Task.Run().ConfigureAwait(false).

Read more about the Task-based asynchronous pattern (TAP).

Here is a short but nice FAQ and tutorial too.

Upvotes: 1

Related Questions