Big Ball of Mud

Thread Marshalling Part 3 – Automatic Marshalling

with one comment

The last posts from this series showed some basic ways of marshalling background operations in the UI. Now that we know how the BackgroundWorker really works, we can take advantage of some .NET infrastructure and build the component, which will automagically (and this is no April 1st joke) marshall cross-thread calls depending on the execution context. This means we can use it in Windows Forms, WPF, WCF and any other environment which support SynchronizationContext model (even our own!).

In last posts we were using some seriously CPU-wasting empty looping. Let’s take this up one level: what is the more advanced way to create loops in C#? Iterators of course! The component I will show takes any IEnumerable you provide (from iterator block) and iterates through it asynchronously. Each yield return will jump back to the calling thread to notify about the progress. The code below is a modification to the previous EmptyLoop class:

    1     public class IteratingOperation<T>
    2     {
    3         private readonly IEnumerable<T> _loop;
    4         private AsyncOperation _operation;
    5         private bool _cancelled;
    6
    7         public IteratingOperation(IEnumerable<T> loop)
    8         {
    9             _loop = loop;
   10         }
   11
   12         public void Begin()
   13         {
   14             _operation = AsyncOperationManager.CreateOperation(null);
   15             new Action(Iterate).BeginInvoke(null, null);
   16
   17         }
   18
   19         public void Cancel()
   20         {
   21             _cancelled = true;
   22         }
   23
   24         private void Iterate()
   25         {
   26             foreach (var result in _loop)
   27             {
   28                 if (_cancelled)
   29                 {
   30                     _operation.PostOperationCompleted(_ => OnCompleted(true), null);
   31                     return;
   32                 }
   33                 var resultToPost = result;
   34                 _operation.Post(_ => OnUpdateProgress(resultToPost), null);
   35             }
   36             _operation.PostOperationCompleted(_ => OnCompleted(true), null);
   37         }
   38
   39         protected virtual void OnUpdateProgress(T progress)
   40         {
   41             if (UpdateProgress != null)
   42                 UpdateProgress(this, new UpdateProgressEventArgs(progress));
   43         }
   44
   45         protected virtual void OnCompleted(bool cancelled)
   46         {
   47             if (Completed != null)
   48                 Completed(this, new CompletedEventArgs(cancelled));
   49         }
   50
   51         public event EventHandler<UpdateProgressEventArgs> UpdateProgress;
   52         public event EventHandler<CompletedEventArgs> Completed;
   53
   54         public class UpdateProgressEventArgs : EventArgs
   55         {
   56             public T Progress { get; private set; }
   57
   58             public UpdateProgressEventArgs(T progress)
   59             {
   60                 Progress = progress;
   61             }
   62         }
   63
   64         public class CompletedEventArgs : EventArgs
   65         {
   66             public bool Cancelled { get; private set; }
   67
   68             public CompletedEventArgs(bool cancelled)
   69             {
   70                 Cancelled = cancelled;
   71             }
   72         }
   73     }

As you can see, the Begin method calls the Iterate method asynchronously and creates an AsyncOperation instance using AsyncOperationManager class. Both internally handle the appropriate SynchronizationContext. The Iterate method runs the provided loop as usual and calls the events, but uses the AsyncOperation instance to call this on the thread that created the operation. This gives us the safe way to publish these events to the UI. We do not use some parameter-passing features of AsyncOperation, hence lots of nulls in the code.

Because we pass lambdas to Post methods, there is one more interesting line here. If we just pass the result in this line like this:

   34                 _operation.Post(_ => OnUpdateProgress(result), null);

the result variable belongs to the outer scope of the lambda we are passing. This creates a closure and if the result variable gets modified in some way before the lambda is called, the modified version will be used. As the lambda will be called on another thread, there is a chance that the iterating loop will manage to modify it. This is a case when we have a state shared accross threads – the risk I am not willing to take. That’s why I get rid of the modified closure the usual way:

   33                 var resultToPost = result;
   34                 _operation.Post(_ => OnUpdateProgress(resultToPost), null);

The last thing I want to show is the usage of this component on the form (the form itself is the modified version from earlier examples):

    1 public partial class FormWithIterator : Form
    2     {
    3         private IteratingOperation<int> _emptyLoopOperation;
    4
    5         public FormWithIterator()
    6         {
    7             InitializeComponent();
    8         }
    9
   10         private void bStart_Click(object sender, EventArgs e)
   11         {
   12             progressBar.Visible = true;
   13
   14             bCancel.Enabled = true;
   15
   16             _emptyLoopOperation = new IteratingOperation<int>(DoEmptyLoop());
   17             _emptyLoopOperation.UpdateProgress += emptyloop_UpdateProgress;
   18             _emptyLoopOperation.Completed += emptyloop_Completed;
   19             _emptyLoopOperation.Begin();
   20         }
   21
   22         public IEnumerable<int> DoEmptyLoop()
   23         {
   24             var step = 10000000;
   25             for (long i = 0; i < 1000000000; i++)
   26             {
   27                 if (i % step == 0) yield return (int)(i / step);
   28             }
   29         }
   30
   31         private void ShowProgress(int percent)
   32         {
   33             progressBar.Value = percent;
   34         }
   35
   36
   37         private void ShowAlgorithmCompleted()
   38         {
   39             progressBar.Value = 0;
   40             progressBar.Visible = false;
   41             bCancel.Enabled = false;
   42         }
   43
   44
   45         private void emptyloop_UpdateProgress(object sender, IteratingOperation<int>.UpdateProgressEventArgs e)
   46         {
   47             ShowProgress(e.Progress);
   48         }
   49
   50
   51         private void emptyloop_Completed(object sender, IteratingOperation<int>.CompletedEventArgs e)
   52         {
   53             ShowAlgorithmCompleted();
   54         }
   55
   56
   57         private void bCancel_Click(object sender, EventArgs e)
   58         {
   59             _emptyLoopOperation.Cancel();
   60         }
   61     }

As you can see the DoEmptyLoop doesn’t need to know anything about threading and thread marshalling, just like in the case of BackgroundWorker.

Advertisements

Written by bigballofmud

2009/04/01 at 10:43 pm

Posted in C#, Multithreading

One Response

Subscribe to comments with RSS.

  1. Thanks for the post. It helps a lot.

    Xeenych

    2009/04/10 at 7:08 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: