Invoke() and BeginInvoke() behaving differently when executing an overridable method via a delegate
Asked Answered
V

3

7

Can anyone tell me why this code behaves the way it does? See comments embedded in the code...

Am I missing something really obvious here?

using System;
namespace ConsoleApplication3
{
    public class Program
    {
        static void Main(string[] args)
        {
            var c = new MyChild();
            c.X();
            Console.ReadLine();
        }
    }

    public class MyParent
    {
        public virtual void X()
        {
            Console.WriteLine("Executing MyParent");
        }
    }

    delegate void MyDelegate();

    public class MyChild : MyParent
    {
        public override void X()
        {
            Console.WriteLine("Executing MyChild");
            MyDelegate md = base.X;

            // The following two calls look like they should behave the same,
            //  but they behave differently!    

            // Why does Invoke() call the base class as expected here...
            md.Invoke();

            // ... and yet BeginInvoke() performs a recursive call within
            //  this child class and not call the base class?
            md.BeginInvoke(CallBack, null);
        }

        public void CallBack(IAsyncResult iAsyncResult)
        {
            return;
        }
    }
}
Vasques answered 23/10, 2008 at 12:14 Comment(1)
I have not tried this, or was aware there was a problem, but I can see a lot of issues coming from this. Perhaps someone can explain :)Uraemia
O
5

I don't have an answer yet, but I have what I believe to be a slightly clearer program to demonstrate the oddity:

using System;

delegate void MyDelegate();

public class Program
{
    static void Main(string[] args)
    {
        var c = new MyChild();
        c.DisplayOddity();
        Console.ReadLine();
    }
}

public class MyParent
{
    public virtual void X()
    {
        Console.WriteLine("Executing MyParent.X");
    }
}

public class MyChild : MyParent
{
    public void DisplayOddity()
    {
        MyDelegate md = base.X;

        Console.WriteLine("Calling Invoke()");
        md.Invoke();                // Executes base method... fair enough

        Console.WriteLine("Calling BeginInvoke()");
        md.BeginInvoke(null, null); // Executes overridden method!
    }

    public override void X()
    {
        Console.WriteLine("Executing MyChild.X");
    }
}

This doesn't involve any recursive calls. The result is still the same oddity though:

Calling Invoke()
Executing MyParent.X
Calling BeginInvoke()
Executing MyChild.X

(If you agree that this is a simpler repro, feel free to replace the code in the original question and I'll remove it from my answer :)

To be honest, this looks like a bug to me. I'll dig around a bit more.

Orangeade answered 23/10, 2008 at 12:27 Comment(5)
It looks like a bug with the internal code generated for BeginInvoke. Looking at the stacktrace of the 2nd call, confirms the 'correctness' of the method info in the delegate (still MyParent.X).Uraemia
Another oddity is, why is Remoting being used for an async call? I really thought it would just use a simple thread or threadpool.Uraemia
Where do you see Remoting coming in?Orangeade
Place a breakpoint on the derived method. Compare that to a normal threadpool or threaded call. There seems to be a lot of 'remoting' stuff in there.Uraemia
Oh yes. Found out this about it: blogs.msdn.com/cbrumme/archive/2003/07/14/51495.aspx - which also talks about virtual/non-virtual stuff...Orangeade
U
1

Maybe not the answer you are looking for, but this seems to work:

ThreadPool.QueueUserWorkItem(x => md());

or

new Thread(() => md()).Start();

But you will need to do your own accounting :(

Uraemia answered 23/10, 2008 at 14:53 Comment(0)
F
1

While Delegate.Invoke calls the delegate method directly, Delegate.BeginInvoke internally uses ThreadPool.QueueUserWorkItem( ). md.Invoke() was only able to call base.X because a base class's methods are accessible within the derived class through the base keyword. Since the delegate started by the thread pool is external to your class, the reference to its X method is subjected to overloading, just like the code below.



    public class Program
    {
        static void Main(string[] args)
        {
            MyChild a = new MyChild();
            MyDelegate ma = new MyDelegate(a.X);

            MyParent b = new MyChild();
            MyDelegate mb = new MyDelegate(b.X);

            ma.Invoke();
            mb.Invoke();
            ma.BeginInvoke(CallBack, null);
            mb.BeginInvoke(CallBack, null); //all four calls call derived MyChild.X

            Console.ReadLine();
        }

        public static void CallBack(IAsyncResult iAsyncResult)
        {
            return;
        }
    }

Debug into .NET Framework code: http://blogs.msdn.com/sburke/archive/2008/01/16/configuring-visual-studio-to-debug-net-framework-source-code.aspx

Fluoresce answered 1/11, 2008 at 1:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.