0

I have BeginInvoke a delegate which internally invokes multiple asynchronous operations and i want to wait for all internal operations to complete before callback for main asynchronous operation executes.

I could have easily achieved that using async, await or TPL but can't since my target platform is .Net3.5. Sample code demonstrating my problem -

class Program
{
    static List<string> abc = new List<string>();
    static void Main(string[] args)
    {
        new Action(() =>
            {
                A();
            }).BeginInvoke(MainCallback, null);
    }

    static void MainCallback(IAsyncResult result)
    {
        foreach (string str in abc)
        {
            Console.WriteLine(String.Format("Main Callback {0}",str));
        }
    }

    static void A()
    {
        for (int i = 0; i < 10; i++)
        {
            new Action(() =>
                {
                    Thread.Sleep(1000);
                }).BeginInvoke(Callback, i);
        }
    }

    static void Callback(IAsyncResult result)
    {
        abc.Add(result.AsyncState.ToString());
    }
}

I want the output to be something like this -

Main Callback 0
Main Callback 1
Main Callback 2
Main Callback 3
Main Callback 4
Main Callback 5
Main Callback 6
Main Callback 7
Main Callback 8
Main Callback 9
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185

2 Answers2

4

You need to track how many operations you've started.

Then, in each callback, decrement this counter (to indicate that one of the operations is finished).

When it reaches 0, everything is finished, so you can call the final callback.

This is how Task.WhenAll() works.

Since this is a multi-threaded environment, you must use Interlocked.Decrement (carefully).

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Thanks. You mentioned `carefully`. Is this good enough - `Interlocked.Decrement(ref counter); abc.Add(result.AsyncState.ToString()); if (counter == 0) MainCallback(null);` or i need to do something more? – Rohit Vats Aug 08 '13 at 19:49
  • 1
    @RohitVats: No; `counter` may change between those two lines. You need to check the return value of `Decrement()`. – SLaks Aug 08 '13 at 19:56
  • `counter = Interlocked.Decrement(ref counter);` - is this what you meant? And do i need to wrap entire code in `lock`? – Rohit Vats Aug 08 '13 at 20:01
  • 1
    @RohitVats: No; that's even worse. If a different thread decrements it at the same time, its changes will be swallowed. YOu need to check whether the return value is `0`. – SLaks Aug 08 '13 at 20:04
  • You can use `Interlocked` to write lock-free thread-safe code. However, you need to learn more about thread-safety. See also my blog post, http://blog.slaks.net/2013-07-22/thread-safe-data-structures – SLaks Aug 08 '13 at 20:05
  • Thanks i will read on that for sure. Will this suffice - `if (Interlocked.Decrement(ref counter) == 0) MainCallback(null);`? – Rohit Vats Aug 08 '13 at 20:08
-1

You could achieve expected output by modifying little of your code. Modify your A method as below. You're done

static void A()
{
    List<IAsyncResult> results = new List<IAsyncResult>();
    for (int i = 0; i < 10; i++)
    {
        results.Add(new Action(() =>
        {
            Thread.Sleep(1000);
        }).BeginInvoke(Callback, i));
    }

    foreach (var res in results)
    {
        res.AsyncWaitHandle.WaitOne();
    }
} 

Does this helps?

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189