Bogdan Varlamov (a.k.a. Phantom Stranger)

Lambdas, Extension Methods, and Asynchronous Execution Example


by Bogdan Varlamov (a.k.a. Phantom Stranger)

I've often found myself working on a class in some legacy application using objects hidden away in third-party libraries and thinking, "Wow... it would be really great if I could call this current existing method but have it process asynchronously (without blocking) and then notify me when it is complete."

The only way to do this would be to inherit the legacy object and add a new "Async" version of the method, right? And what about those pesky sealed classes? Not to mention the problems with having to refactor your whole application (and perhaps any dependent components used by other applications).

It can be quite a mess! But, luckily there is a simpler way...

What I've done is create an extension library that allows any object to do asynchronous processing.

Here is an example of how to use this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using _5DSolutions.Extensions.AsyncHandler; //here are the extensions
 
namespace Example
{
    public class Program
    {
        static long _asyncComplesCounter = 0;
        static long _disposedCounter = 0;
 
        static void Main(string[] args)
        {
            GoCrazy();
        }
 
        private static void GoCrazy()
        {
            //here we have some "legacy" object that we cant update/add new methods to
            TestObject tmp = new TestObject();
 
            int counter;
 
            Console.WriteLine("How many times would you like to asynchronously print?");
 
            while (!int.TryParse(Console.ReadLine(), out counter))
            {
                Console.WriteLine("Enter an integer number only, less than {0}", int.MaxValue);
            }
 
            while (counter > 0)
            {
                //here we are using the extension method on some "TestObject" class that we can't modify
                tmp.HandleAsynchronously((sender, e) =>
                    {
                        Console.WriteLine("Asynchronously calling a synchronous method");
                        tmp.DoSomething();//this blocks
                        Console.WriteLine("Woohoo! We are done blocking the synchronous method call, notifying through the completed event handler...");
                    },
                    (sender, e) =>
                    {
 
                        System.Threading.Interlocked.Increment(ref _asyncComplesCounter);
                        Console.WriteLine("---An Asynch write has finished! -- {0}", System.Threading.Interlocked.Read(ref _asyncComplesCounter));
 
                    },
                    null,
                    (sender, e) =>
                    {
                        System.Threading.Interlocked.Increment(ref _disposedCounter);
                        Console.WriteLine(">>>>>>>>> disposed");
                    },
                    null);
                counter--;
            }
 
            Console.ReadLine();
            tmp = null;
            GC.Collect();
            Console.WriteLine("Total completed events: {0}", _asyncComplesCounter);
            Console.WriteLine("Total disposed bg-workers: {0}", _disposedCounter);
 
            Console.WriteLine("Go again?");
 
            if (Console.ReadLine().Equals("Yes", StringComparison.InvariantCultureIgnoreCase))
            {
                GoCrazy();
            }
            else
            {
                //just exit
            }
        }
    }
}

There is nothing tricky going on with the TestObject class. In fact, you should be able to use your own class. Or you can use this test object:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Example
{
    public class TestObject
    {
        public TestObject()
        {
 
        }
 
        public void DoSomething()
        {
            Console.WriteLine("Synchronously writing this then waiting for 0.5 seconds...");
            System.Threading.Thread.Sleep(500);
            Console.WriteLine("--Synchronous write Done.");
        }
    }
}

The real magic actually lives in the Extensions project:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
 
namespace _5DSolutions.Extensions.AsyncHandler
{
    public static class ObjectAsyncExtension
    {
 
        public static void HandleAsynchronously(this Object caller, DoWorkEventHandler onDoWork, RunWorkerCompletedEventHandler onCompleted)
        {
            HandleAsynchronously(caller, onDoWork, onCompleted, null, null, null);
        }
 
        public static void HandleAsynchronously(this Object caller, DoWorkEventHandler onDoWork, RunWorkerCompletedEventHandler onCompleted, ProgressChangedEventHandler onProgressChanged, EventHandler onDisposed, object argument)
        {
            //check required params
            if (null == onDoWork)
                throw new ArgumentNullException("onDoWork");
 
            if (null == onCompleted)
                throw new ArgumentNullException("onCompleted");
 
            BackgroundWorker tmp = new BackgroundWorker();
 
            tmp.DoWork += onDoWork;
            tmp.RunWorkerCompleted += onCompleted;
            tmp.RunWorkerCompleted += (sender, e) =>
            {//note, we subscribe twice to "dispose" of the object
                //and raise the Disposed event
                tmp.DoWork -= onDoWork;
                tmp.RunWorkerCompleted -= onCompleted;
                tmp.ProgressChanged -= onProgressChanged;
                tmp.Dispose();
                tmp = null;
            };
 
            if (null != onProgressChanged)
            {
                tmp.WorkerReportsProgress = true;
                tmp.ProgressChanged += onProgressChanged;
            }
 
            if (null != onDisposed)
            {
                tmp.Disposed += onDisposed;
                tmp.Disposed += (sender, e) =>
                    {//i dont think this is really necessary...
                        //but leaving it in for reference
                        tmp.Disposed -= onDisposed;
                    };
            }
 
            if (null != argument)
                tmp.RunWorkerAsync(argument);
            else
                tmp.RunWorkerAsync();
        }
 
    }
}

Of course you can substitute your own named delegates for the lambdas in the Program class in this example, or your own lambdas to do actual work.

The main concern I had with this implementation was whether or not this code would be subject to thread leaks.

I tested this code with 30k calls to HandleAsynchronously and watched the threads/memory/handles fluctuate, but always drop back down as garbage collection was performed. (I am explicitly calling it in the code samples above to better observe potential issues, but it should happen naturally without the need for an explicit call).

--

The above source code is © 2009 by Bogdan Varlamov and may only be used if you agree to the BSD License Terms.


Rss Comment

No Comment

No comments yet.

Post a Comment





This is a captcha-picture. It is used to prevent mass-access by robots. (see: www.captcha.net)

You must read and type the 5 chars within 0..9 and A..F, and submit the form.

  

Oh no, I cannot read this. Please, generate a