Two questions about AsyncCallback and IAsyncResult pattern

47.3k views Asked by At

Two questions on the callback pattern with AsyncCallback and IAsyncResult.

I changed the question with a code example:

using System;
using System.Collections.Generic;
using System.Text;

namespace TestAsync
{
    class Program
    {
        private static Wrapper test = new Wrapper();

        static void Main(string[] args)
        {
            test.BeginMethod("parameter 1", "parameter 2", Callback);
            Console.ReadKey();
        }

        private static void Callback(IAsyncResult ar)
        {
            string result = test.EndMethod(ar);
        }
    }

    public interface ITest
    {
        IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state);
        string EndMethod(IAsyncResult result);
    }

    public class Wrapper
    {
        private ITest proxy = new Test();

        public void BeginMethod(string s1, string s2, AsyncCallback cb)
        {
            proxy.BeginMethod(s1, s2, cb, proxy);
        }

        public string EndMethod(IAsyncResult result)
        {
            return ((ITest)(result.AsyncState)).EndMethod(result);
        }
    }

    public class Test : ITest
    {
        private string WorkerFunction(string a, string b)
        {
            // "long running work"
            return a + "|" + b;
        }

        public IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state)
        {
            Func<string, string, string> function = new Func<string, string, string>(WorkerFunction);
            IAsyncResult result = function.BeginInvoke(s1, s2, cb, state);
            return result;
        }

        public string EndMethod(IAsyncResult result)
        {
            return (string)(result.AsyncState);
        }
    }

    public delegate TResult Func<T1, T2, TResult>(T1 t1, T2 t2);
}

BEGIN EDIT
I am beginning to see what is going on. I have mixed up a WCF async pattern and a normal async pattern. In WCF one uses a proxy and the Begin- and EndMethod must be passed the proxy and not the function delegate. In the WCF case the casting works, in the normal case not. WCF uses the [OperationContract(AsyncPattern = true)] attribute probably to enforce a somewhat different pattern. END EDIT

Why the error on the line return (string)(result.AsyncState); ?
Exactly the same pattern in production code is ok.

Secondly, why can I not debug code in BeginMethod of class Test?
I can only break in WorkerFunction.

2

There are 2 answers

7
Nikhil On BEST ANSWER

Let me give you this sample code to make things a bit clear. Please create a new console app and use this

public class Test
{
    private int WorkerFunction(string a, string b)
    {
        //this is the guy that is supposed to do the long running work 
        Console.WriteLine(a);
        Console.WriteLine(b);
        return a.Length + b.Length;
    }

    private void MyCallBack(IAsyncResult ar)
    {
        Func<string, string, int> function = ar.AsyncState as Func<string, string, int>;
        int result = function.EndInvoke(ar);
        Console.WriteLine("Result is {0}", result);
    }
    public void CallMethod()
    {
        Func<string, string, int> function = new Func<string, string, int>(WorkerFunction);
        IAsyncResult result = function.BeginInvoke("param1", "param2", MyCallBack, function);
    }


}

class Program
{

    static void Main(string[] args)
    {
        Test test = new Test();
        test.CallMethod();
    }
}

As you can see the callback function (MyCallBack) gets an IAsyncResult object passed back to it. It is this IAsynchResult object whose AyncState gives you the original object you had passed in the BeginInvoke method call. In this case (and as a general practice) you pass in the delegate itself as the object (which was the variable called "function"). One the callback was called, I then got the original delegate object back by quering the ar.AsyncState, I then called EndInvoke on it to get back the result.

As for the breakpoint not being hit, I am afraid I need some more information on it. What exactly do you mean? Where is this Console.WriteLine statement?

NEW RESPONSE OK here is my version of your code. Basically no matter where you call the EndInvoke from, you need to call it on the actual delegate object (in your case the "function" variable you instantiate, passing it the actual IAsyncResult object). The code you have is trying to mask this facility however I must say there are less complicated ways of doing this. I will be more than happy to write a wrapper of sorts for you if you wish. For now I am simply giving you your code back with my small addition in it, that should make it work. Since you are using class level variables hence I am forced to use one myself. This is not really thread safe at the moment. But here goes

using System;
using System.Collections.Generic;
using System.Text;

namespace TestAsync
{
    class Program
    {
        private static Wrapper test = new Wrapper();

        static void Main(string[] args)
        {
            var objectState = new object();
            test.BeginMethod("parameter 1", "parameter 2", Callback, objectState);
            Console.ReadKey();
        }

        private static void Callback(IAsyncResult ar)
        {
            string result = test.EndMethod(ar);
            Console.WriteLine(result);
        }
    }

    public interface ITest
    {
        IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state);
        string EndMethod(IAsyncResult result);
    }

    public class Wrapper
    {
        private ITest proxy = new Test();

        public void BeginMethod(string s1, string s2, AsyncCallback cb)
        {
            proxy.BeginMethod(s1, s2, cb, proxy);
        }

        public string EndMethod(IAsyncResult result)
        {
            return ((ITest)(result.AsyncState)).EndMethod(result);
        }
    }

    public class Test : ITest
    {
        Func<string, string, string> _delgateObject;
        private string WorkerFunction(string a, string b)
        {
            // "long running work"
            return a + "|" + b;
        }

        public IAsyncResult BeginMethod(string s1, string s2, AsyncCallback cb, object state)
        {
            Func<string, string, string> function = new Func<string, string, string>(WorkerFunction);
            this._delgateObject = function;
            IAsyncResult result = function.BeginInvoke(s1, s2, cb, state);
            return result;
        }

        public string EndMethod(IAsyncResult result)
        {
            var test = result.AsyncState;
            return this._delgateObject.EndInvoke(result);
        }
    }

    public delegate TResult Func<T1, T2, TResult>(T1 t1, T2 t2);
}
0
Gerard On

This article helped me understand what was going on. Wcf's OperationContract implements a special Async pattern, that synchronously calls [Operation] on a seperate thread. Begin[Operation] and End[Operation] are used to create the pattern but they will not really be invoked. So this pattern with its signatures and attributes seems to be identical with makeing a synchronous call on the client via e.g. a BackgroundWorker.

You can only set AsyncPattern [of OperationContract attribute] to true on a method with a BeginOperation-compatible signature, and the defining contract must also have a matching method with an EndOperation-compatible signature. These requirements are verified at the proxy load time. What AsyncPattern does is bind the underlying synchronous method with the Begin/End pair, and correlates the synchronous execution with the asynchronous one. Briefly, when the client invokes a method of the form BeginOperation with AsyncPattern set to true, it tells WCF not to try to directly invoke a method by that name on the service. Instead, it will use a thread from the thread pool to synchronously call the underlying method (identified by the Action name). The synchronous call will block the thread from the thread pool, not the calling client. The client will only be blocked for the slightest moment it takes to dispatch the call request to the thread pool. The reply method of the synchronous invocation is correlated with the EndOperation method.