Click here to Skip to main content
Click here to Skip to main content

Action Extensions

By , 1 Jul 2008
 

Introduction

Ennis Ray Lynch, Jr. gave me an idea the other day in the Lounge. The basic idea was running multicast delegates in parallel.

Background

The code to run delegates in parallel is rather trivial.

static void Run(Action[] aa)
{
  List<IAsyncResult> waits = new List<IAsyncResult>();

  foreach (Action a in aa)
  {
    waits.Add(a.BeginInvoke(null, null));
  }

  foreach (IAsyncResult ar in waits)
  {
    ar.AsyncWaitHandle.WaitOne();
  }
}

To run a multicast delegate in parallel follows the same pattern.

static void Run(Action t)
{
  List<IAsyncResult> waits = new List<IAsyncResult>();

  foreach (Action a in t.GetInvocationList())
  {
    waits.Add(a.BeginInvoke(null, null));
  }

  foreach (IAsyncResult ar in waits)
  {
    ar.AsyncWaitHandle.WaitOne();
  }
}

Using the Code

Following the above pattern, we simply create stubs for the generic Action delegate (only 1 shown for clarity). If needed, you can replace with your own delegate type. There is one important aspect to keep in mind; the delegate MUST return void. Why, you may ask? The answer is simple. There is no easy way to consume multiple return values in C#.

static class ActionExtensions
{
  class WaitList : IDisposable
  {
    readonly List<IAsyncResult> waits = new List<IAsyncResult>();

    public void Add(IAsyncResult ar)
    {
      waits.Add(ar);
    }

    public void Dispose()
    {
      foreach (var ar in waits)
      {
        ar.AsyncWaitHandle.WaitOne();
      }
    }
  }

  public static Action MakeParallel(this Action t)
  {
    return () =>
    {
      using (var w = new WaitList())
      {
        foreach (Action a in t.GetInvocationList())
        {
          w.Add(a.BeginInvoke(null, null));
        }
      }
    };
  }
}

The usage is also trivial. Simply call the MakeParallel extension method.

class Program
{
  static void Main(string[] args)
  {
    Action<int> f = null;

    for (int i = 0; i < 8; i++)
    {
      f += Thread.Sleep;
    }

    Stopwatch ws = Stopwatch.StartNew();

    f(250);

    Console.WriteLine("ser: {0:f3}", ws.Elapsed.TotalMilliseconds);

    f = f.MakeParallel();

    ws = Stopwatch.StartNew();

    f(250);

    Console.WriteLine("par: {0:f3}", ws.Elapsed.TotalMilliseconds);

    Console.ReadLine();
  }
}

The output should show the parallel version running at half (or quarter) the time of the serial version.

Points of Interest

From what I can determine, BeginInvoke utilizes the number of logical CPUs. I have however not been able to test this.

History

  • 2nd July, 2008 - Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

leppie
Software Developer
South Africa South Africa
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralNice and simple.mvpSacha Barber6 Jan '11 - 23:15 
I like this. 5
Sacha Barber
  • Microsoft Visual C# MVP 2008-2010
  • Codeproject MVP 2008-2010
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue
 
My Blog : sachabarber.net

GeneralNice, but you should call EndInvoke()memberJelle Hissink6 Jul '08 - 9:48 
First of all a nice idea Smile | :) !
However you should call EndInvoke() because that allows for cleanup of possible leaking resources (waithandles etc. otherwise you depend on the GC), so if you replace
ar.AsyncWaitHandle.WaitOne();
with
a.EndInvoke(ar);
All will be fine (The BeginInvoke/EndInvoke pattern says EndInvoke() should wait for the completion)
 

GeneralRe: Nice, but you should call EndInvoke()memberleppie6 Jul '08 - 10:20 
Cool, thanks Smile | :)
 
xacc.ide - now with TabsToSpaces support
IronScheme - 1.0 alpha 4a out now (29 May 2008)

GeneralSweetmemberNick Butler3 Jul '08 - 22:28 
Very nice, thanks leppie Smile | :) Nothing left to take out here Cool | :cool:
 
I can confirm that BeginInvoke runs 8 at a time on my 8 core box.
 
Nick
 
----------------------------------
Be excellent to each other Smile | :)

GeneralRe: Sweetmemberleppie4 Jul '08 - 7:21 
Nick Butler wrote:
Very nice, thanks leppie

 
Thanks Smile | :)
 
Nick Butler wrote:
I can confirm that BeginInvoke runs 8 at a time on my 8 core box.

 
:drool:
 
xacc.ide - now with TabsToSpaces support
IronScheme - 1.0 alpha 4a out now (29 May 2008)

GeneralNice workmemberkin3tik2 Jul '08 - 5:42 
Dude brilliant stuff ... great to see you still among the living also man
 
Banshee for windows YAY !!!
http://sourceforge.net/projects/banshee32

GeneralNicemvpPete O'Hanlon1 Jul '08 - 22:33 
Like it - it's not something I've ever felt the need to do, but now that you've uploaded it I'm sure I'll find loads of cases to use this. Have yourself a 5.
 
Deja View - the feeling that you've seen this post before.
 

My blog | My articles



GeneralRe: Nicememberleppie1 Jul '08 - 22:48 
Pete O'Hanlon wrote:
it's not something I've ever felt the need to do

 
Me neither, but I like the idea Smile | :)
 
Pete O'Hanlon wrote:
Have yourself a 5.

 
Thanks Smile | :)
 
xacc.ide - now with TabsToSpaces support
IronScheme - 1.0 alpha 4a out now (29 May 2008)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 2 Jul 2008
Article Copyright 2008 by leppie
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid