Asynchronous delegates and callback

I was debugging some issue related to asynchronous delegates and wanted to share that information with everyone.

When you do a BeginInvoke the delegate is invoked on a thread from the thread pool and on completion the callback is also called on the same thread pool thread. Now, any exceptions that is thrown on this thread cannot be caught by the main thread and so will crash the process. If you want to handle the exception, then you have to catch the expected exception in the callback and store it to some state and rethrow the exception from the main thread so that the callers above you can handle it gracefully.

In the sample below if you remove the try catch block in the callback or replace the exception.Add(e) with a throw, then the exception will crash the process. If you swallow the exception then you will be hiding failures and continuing which could result in other failures.

using System;

using System.Threading;

using System.Runtime.Remoting.Messaging;

using System.Collections.Generic;

namespace Delegate

{

    public class SampleClass

    {

        public static bool SampleMethod()

        {

            Console.WriteLine("Inside sample method ...");

            throw new ArgumentException();

        }

    }

    public delegate bool SampleMethodCaller();

    public class DelegateSample

    {

        List<Exception> exceptions;

        ManualResetEvent waiter;

        public DelegateSample()

        {

            exceptions = new List<Exception>();

        }

        public void CallBackMethodForDelegate(IAsyncResult result)

        {

            SampleMethodCaller smd = (SampleMethodCaller)((AsyncResult)result).AsyncDelegate;

            try

            {

                bool returnValue = smd.EndInvoke(result);

            }

      catch (ArgumentException e)

            {

            exceptions.Add(e);

            }

            finally

            {

                waiter.Set();

            }

        }

        public void CallDelegateUsingCallBack()

        {

            try

            {

                waiter = new ManualResetEvent(false);

                SampleMethodCaller smd = new SampleMethodCaller(SampleClass.SampleMethod);

                IAsyncResult result = smd.BeginInvoke(CallBackMethodForDelegate, null);

     waiter.WaitOne();

                if (exceptions.Count != 0)

                {

                    throw exceptions[0];

                }

            }

            catch (ArgumentException)

            {

                Console.WriteLine("Catch exceptions here ...");

            }

        }

        public static void Main()

        {

            DelegateSample ds = new DelegateSample();

            ds.CallDelegateUsingCallBack();

            Console.WriteLine(" -----------------");

        }

    }

}