Search code examples
c#asynchronousbegininvokewaithandlewaitone

How can I use WaitHandle awaiting completion of an asynchronous call?


Consider this code:

class Program
        {
            static void Main(string[] args)
            {
                Master master = new Master();
                master.Execute();
            }
        }

    class TestClass
    {
        public void Method(string s)
        {
            Console.WriteLine(s);
            Thread.Sleep(5000);
            Console.WriteLine("End Method()");
        }
    }
    class Master
    {
        private readonly TestClass test = new TestClass();

        public void Execute()
        {
            Console.WriteLine("Start main thread..");
            Action<String> act = test.Method;
            IAsyncResult res = act.BeginInvoke("Start Method()..", x =>
            {
                Console.WriteLine("Start Callback..");
                act.EndInvoke(x);
                Console.WriteLine("End Callback");
            }, null);
            Console.WriteLine("End main thread");
            Console.ReadLine();
        }
    }

We have result:

Start main thread..
End main thread
Start Method()..
End Method()
Start Callback..
End Callback

So, I want result:

Start main thread..    
Start Method()..
End Method()
Start Callback..
End Callback
End main thread

How can I wait async in this code? I checked MSDN article "Calling Synchronous Methods Asynchronously" and found this:

After calling BeginInvoke you can do the following:

  • Do some work and then call EndInvoke to block until the call completes.
  • Obtain a WaitHandle using the IAsyncResultAsyncWaitHandle
    property, use its WaitOne method to block execution until the
    WaitHandle is signaled, and then call EndInvoke.
  • Poll the IAsyncResult returned by BeginInvoke to determine when the asynchronous call has completed, and then call EndInvoke.
  • Pass a delegate for a callback method to BeginInvoke. The method is executed on a ThreadPool thread when the asynchronous call completes. The callback method calls EndInvoke.

I think better variant for me this second. But how implement this? In particular I'm interested overload WaitOne() (Blocks the current thread until the current WaitHandle receives a signal). How correctly do it? I mean the common pattern in this case.

UPDATE:

Now I use Task<T>:

 class Program
    {
        static void Main(string[] args)
        {
            Master master = new Master();
            master.Execute();
        }
    }

    class WebService
    {
        public int GetResponse(int i)
        {
            Random rand = new Random();
            i = i + rand.Next();
            Console.WriteLine("Start GetResponse()");
            Thread.Sleep(3000);
            Console.WriteLine("End GetResponse()");
            return i;
        }

        public void SomeMethod(List<int> list)
        {
            //Some work with list        
            Console.WriteLine("List.Count = {0}", list.Count);
        }
    }
    class Master
    {
        private readonly WebService webService = new WebService();

        public void Execute()
        {
            Console.WriteLine("Start main thread..");
            List<int> listResponse = new List<int>();
            for (int i = 0; i < 5; i++)
            {
                var task = Task<int>.Factory.StartNew(() => webService.GetResponse(1))
                    .ContinueWith(x =>
                    {
                        Console.WriteLine("Start Callback..");
                        listResponse.Add(x.Result);
                        Console.WriteLine("End Callback");
                    });
            }
            webService.SomeMethod(listResponse);
            Console.WriteLine("End main thread..");
            Console.ReadLine();
        }
    }

Main problem this is SomeMethod() gets empty list.

Result: enter image description here

Now I have monstrous solution :(

 public void Execute()
        {
            Console.WriteLine("Start main thread..");
            List<int> listResponse = new List<int>();
            int count = 0;
            for (int i = 0; i < 5; i++)
            {
                var task = Task<int>.Factory.StartNew(() => webService.GetResponse(1))
                    .ContinueWith(x =>
                    {
                        Console.WriteLine("Start Callback..");
                        listResponse.Add(x.Result);
                        Console.WriteLine("End Callback");
                        count++;
                        if (count == 5)
                        {
                            webService.SomeMethod(listResponse);
                        }
                    });

            }
            Console.WriteLine("End main thread..");
            Console.ReadLine();
        }

Result:

enter image description here

That's what I need to wait for the asynchronous call. How can I use Wait for Task here?

UPDATE 2:

class Master
{
    private readonly WebService webService = new WebService();
    public delegate int GetResponseDelegate(int i);

    public void Execute()
    {
        Console.WriteLine("Start main thread..");
        GetResponseDelegate act = webService.GetResponse;
        List<int> listRequests = new List<int>();
        for (int i = 0; i < 5; i++)
        {
            act.BeginInvoke(1, (result =>
            {                    
                int req = act.EndInvoke(result);
                listRequests.Add(req);
            }), null);  
        }

        webService.SomeMethod(listRequests);
        Console.WriteLine("End main thread..");
        Console.ReadLine();
    }
}

Solution

  • I assume that is what you want. We are starting tasks and then waiting them to complete, after all are completed, we are getting results.

    class Program
    {
        static void Main(string[] args)
        {
            Master master = new Master();
            master.Execute();
        }
    }
    
    class WebService
    {
        public int GetResponse(int i)
        {
            Random rand = new Random();
            i = i + rand.Next();
            Console.WriteLine("Start GetResponse()");
            Thread.Sleep(3000);
            Console.WriteLine("End GetResponse()");
            return i;
        }
    
        public void SomeMethod(List<int> list)
        {
            //Some work with list        
            Console.WriteLine("List.Count = {0}", list.Count);
        }
    }
    class Master
    {
        private readonly WebService webService = new WebService();
        public void Execute()
        {
            Console.WriteLine("Start main thread..");
            var taskList = new List<Task<int>>();
            for (int i = 0; i < 5; i++)
            {
                Task<int> task = Task.Factory.StartNew(() => webService.GetResponse(1));
                taskList.Add(task);
            }
    
            Task<List<int>> continueWhenAll =
                Task.Factory.ContinueWhenAll(taskList.ToArray(),
                                tasks => tasks.Select(task => task.Result).ToList());
    
            webService.SomeMethod(continueWhenAll.Result);
            Console.WriteLine("End main thread..");
            Console.ReadLine();
        }
    
    }
    

    Ugly solution with BeginIvoke/EndInvoke

    public void Execute()
    {
        Func<int, int> func = webService.GetResponce;
    
        var countdownEvent = new CountdownEvent(5);
        var res = new List<int>();
        for (int i = 0; i < 5; ++i)
        {
            func.BeginInvoke(1, ar =>
                            {
                                var asyncDelegate = (Func<int, int>)((AsyncResult)ar).AsyncDelegate;
                                int ii = asyncDelegate.EndInvoke(ar);
                                res.Add(ii);
                                ((CountdownEvent)((AsyncResult)ar).AsyncState).Signal();
                            }, countdownEvent);
        }
        countdownEvent.Wait();
        Console.WriteLine(res.Count);
    }