Two questions about AsyncCallback and IAsyncResult pattern
Asked Answered
D

2

16

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.

Distich answered 23/2, 2011 at 9:34 Comment(0)
P
29

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);
}
Pastorship answered 23/2, 2011 at 11:12 Comment(7)
You fill in the delegate function as the object @object state parameter - do you know whether that is mandatory?Distich
Instead of IAsyncResult result = function.BeginInvoke(... could you also just do function.BeginInvoke(... ?Distich
Yes sorry that CallBack should be WorkerFunction. I will change this now. It is not mandatory to fill in the delegate function as the @object state parameter. However as you can see doing that gives you access to the delegate within the Callback's body. You do need to call an EndInvoke on the delegate from within the callback so you do need the delegate function reference there. And yes you can simply do function.BeginInvoke. I kept the sample to the same syntax you were using really. As you can see I don't reuse the "result" variable after the BeginInvokePastorship
IMO it is written in a slightly more complicated style then it is supposed to be. I can see where your problem lies. Give me a few minutes and I will fix this up for you.Pastorship
Ok, I changed my question because I realised that I used an extra wrapper object and interface. I was investigating a wcf async pattern that gives an execution error when it is used as a normal async pattern.Distich
The line return (string)(result.AsyncState); is giving you an error as the object in result.AsyncStatus is actually not the result of the WorkerFunction call but the proxy object you created in line private ITest proxy = new Test(); You do have a few things mixed up actually. Let me see if I can help sort this out a bitPastorship
@Pastorship I have fixed all errors. Pls have a look at the code again.Gebler
D
2

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.

Distich answered 23/2, 2011 at 22:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.