masiton
masiton

Reputation: 187

Report Background Thread Progress on UI Thread

I'd like to achieve following result - UI thread registers the progress changed event of my time consuming operation and then runs the method "DoOperationAsync()". The operation will then report back a progress change, but: The event has to be invoked on the UI thread, which I am having problem achieving. The event fires, but when I try to update UI, I need to use Dispatcher, because the event is fired from the thread doing the operation. I don't feel like my library should force the developer to think ahead and use dispatchers everywhere.

Basically I'd like to do what BackgroundWorker does. How does BackgroundWorker fire an ProgressChanged event on the thread that created it?

Upvotes: 4

Views: 3191

Answers (3)

Paul Turner
Paul Turner

Reputation: 39695

If you're using .NET 4.5, you'll have access to the latest version of TPL changes, which includes the IProgress<T> interface and its concrete implementation Progress<T>. The interface was designed to support progress reporting between two asynchronous tasks, specifically the background-to-UI-thread reporting you're after.

The interface itself is simple, defining the Report(T) method as the mechanism for passing a progress update of type T to the other task. When you have some progress to report, you invoke the operation. If you wanted to pass a percentage progress, you could pass 0.1 to an IProgress<float> instance to report 10% progress.

private async Task BackgroundWorkAsync(IProgress<float> progress)
{
    ...

    progress.Report(0.1); // 10%

    ...

    progress.Report(1.0); // 100%
}

The UI thread is expected to create the concrete Progress<T> instance and pass it into the scope of the background task. Progress<T> provides a ProgressChanged event you can subscribe to, but normally you pass an action to the constructor to be called each time progress is updated:

var progress = new Progress(value => // set progress bar);

await this.BackgroundWorkAsync(progress);

This is a crude example, but it shows the magic of how Progress<T> synchronizes the callback according to the context, which in this case would be the UI thread.

Upvotes: 5

DRapp
DRapp

Reputation: 48179

Quite a while ago, and don't have the code to remember exactly how I did it, but here is the basis of what I did. In my UI object where I created my background worker, I also created a method that gets triggered when progress is reported. Then, in the background worker, I set public properties that I could read from and put into my UI.

public class myUIFormControlWhatever
{
   ...
   public void CallTheBackgroundWorker()
   {
      myBackgroundWorker bgw = new myBackgroundWorker();
      // attach "listening" when the background worker reports changes
      bgw.ProgressChanged += thisObjectShowChangedProgress;
      bgw.RunWorkerAsync();
   }

   protected void thisObjectShowChangedProgress( object sender, ProgressChangedEventArgs e )
   {
      this.SomeTextShownOnUI = ((myBackgroundWorker)sender).ExposedProperty;
   }
}


public class myBackgroundWorker : BackgroundWorker
{
   public myBackgroundWorker()
   {
      WorkerReportsProgress = true;
      // hook up internal to background worker any strings
      // you want to expose once reporting and any other listeners are out there.
      ProgressChanged += StatusUpdate;
   }

   protected void StatusUpdate( object sender, ProgressChangedEventArgs e )
   {
      // set property to what you want any other listeners to grab/display
      ExposedProperty = "something you are handling internally to background worker";
   }

   public string ExposedProperty
   { get; protected set; }

}

Again, most of this is from memory with lookup of event handler parameter arguments I couldn't remember signature of. So the UI creates the background worker but listens to any reported changes by hooking up to the "ProgressChanged" event. So once the background worker does it's thing, the UI component handles it's own segment by looking at properties that would be visible to read from the "object sender" parameter which is the background worker itself.

Upvotes: 0

Matthew Watson
Matthew Watson

Reputation: 109862

A BackgroundWorker uses the Event-based asynchronous pattern.

Internally, it uses an instance of class AsyncOperation to raise the event.

Specifically, it calls AsyncOperation.Post() to raise the event on the appropriate thread or context.

You should be able to do that with your library code.

Upvotes: 2

Related Questions