Using C# method group executes code
Asked Answered
B

2

14

While updating my UI code (C# in a .NET 4.0 application), I ran into a strange crash due to a call to the UI being executed in the wrong thread. However, I was invoking that call on the main thread already, so the crash made no sense: MainThreadDispatcher.Invoke(new Action(View.Method)) crashed with "The calling thread cannot access this object because a different thread owns it." on the View property.

Upon further investigation I found the cause: I was invoking via a method group. I had thought that using a method group or a delegate/lambda are essentially the same thing (see also this question and this question). Instead, converting the method group to a delegate causes code to execute, checking the value of View. This is done immediately, i.e. on the original (non-UI) thread, which caused the crash. If I use a lambda instead, checking the property is done later, and thus in the correct thread.

That seems interesting, to say the least. Is there anyplace in the C# standard where this is mentioned? Or is that implicit due to the need to find the correct conversion?

Here's a test program. First, the direct way. Second, in two steps, which better shows what happens. For additional fun, I then modify Item after the delegate has been created.

namespace ConsoleApplication1 // Add a reference to WindowsBase to a standard ConsoleApplication
{
    using System.Threading;
    using System.Windows.Threading;
    using System;

    static class Program
    {
        static Dispatcher mainDispatcher;
        static void Main()
        {
            mainDispatcher = Dispatcher.CurrentDispatcher;
            mainDispatcher.Thread.Name = "Main thread";
            var childThread = new Thread(() =>
                {
                    Console.WriteLine("--- Method group ---");
                    mainDispatcher.Invoke(new Action(Item.DoSomething));

                    Console.WriteLine("\n--- Lambda ---");
                    mainDispatcher.Invoke(new Action(() => Item.DoSomething()));

                    Console.WriteLine("\n--- Method group (two steps) ---");
                    var action = new Action(Item.DoSomething);
                    Console.WriteLine("Invoking");
                    mainDispatcher.Invoke(action);

                    Console.WriteLine("\n--- Lambda (two steps) ---");
                    action = new Action(() => Item.DoSomething());
                    Console.WriteLine("Invoking");
                    mainDispatcher.Invoke(action);

                    Console.WriteLine("\n--- Method group (modifying Item) ---");
                    action = new Action(Item.DoSomething);
                    item = null;
                    mainDispatcher.Invoke(action);
                    item = new UIItem();

                    Console.WriteLine("\n--- Lambda (modifying Item) ---");
                    action = new Action(() => Item.DoSomething());
                    item = null;
                    Console.WriteLine("Invoking");
                    mainDispatcher.Invoke(action);

                    mainDispatcher.InvokeShutdown();
                });
            childThread.Name = "Child thread";
            childThread.Start();

            Dispatcher.Run();
        }

        static UIItem item = new UIItem();
        static UIItem Item
        {
            get
            {
                // mainDispatcher.VerifyAccess(); // Uncomment for crash.
                Console.WriteLine("UIItem: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
                return item;
            }
        }

        private class UIItem
        {
            public void DoSomething()
            {
                Console.WriteLine("DoSomething: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
            }
        }
    }
}

Short version:

namespace ConsoleApplication1 // Add a reference to WindowsBase to a standard ConsoleApplication
{
    using System.Threading;
    using System.Windows.Threading;
    using System;

    static class Program
    {
        static Dispatcher mainDispatcher;
        static void Main()
        {
            mainDispatcher = Dispatcher.CurrentDispatcher;
            mainDispatcher.Thread.Name = "Main thread";
            var childThread = new Thread(() =>
                {
                    Console.WriteLine("--- Method group ---");
                    mainDispatcher.Invoke(new Action(Item.DoSomething));

                    Console.WriteLine("\n--- Lambda ---");
                    mainDispatcher.Invoke(new Action(() => Item.DoSomething()));    

                    mainDispatcher.InvokeShutdown();
                });
            childThread.Name = "Child thread";
            childThread.Start();

            Dispatcher.Run();
        }

        static UIItem item = new UIItem();
        static UIItem Item
        {
            get
            {
                mainDispatcher.VerifyAccess();
                Console.WriteLine("UIItem: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
                return item;
            }
        }

        private class UIItem
        {
            public void DoSomething()
            {
                Console.WriteLine("DoSomething: In thread: {0}", Dispatcher.CurrentDispatcher.Thread.Name);
            }
        }
    }
}
Burglarious answered 30/11, 2011 at 16:5 Comment(5)
Which call fails? What's the call stack?Decreasing
Remove the commented line with VerifyAccess(), and you'll see that all the calls which use method groups fail, since the Item property is accessed on the child thread.Burglarious
A short but complete program just showing the problematic call would be really helpful.Release
I don't see the mystery. The Action class constructor runs on the worker thread. So the Item property getter does too. Unless you use it in the lambda.Deventer
@HansPassant: That's not a regular constructor (it reads the expression to get the this); I can see why he was confused.Decreasing
S
4

The fact that the property will be eagerly accessed is not special to method-group members in any way; it's a characteristic of member-expressions in general.

It's actually the lambda that's creating the special case: its body (and thus the property-access) will be deferred until the delegate is actually executed.

From the specification:

7.6.4 Member access

[...] A member-access is either of the form E.I or of the form E.I, where E is a primary-expression.

[...] if E is a property or indexer access, then the value of the property or indexer access is obtained (§7.1.1) and E is reclassified as a value.

Selfstarter answered 30/11, 2011 at 16:17 Comment(0)
D
6

You're creating a closed delegate, which stores the this object inside the delegate. (to pass as the hidden first parameter to the method.)

Therefore, when you create a delegate from a method group, the object is accessed immediately to store in the delegate.

By contrast, when you create a lambda expression, the object owning the delegate is only accessed when the delegate is called.
Your lambda expressions creates an open delegate which accesses the static property directly within the delegate.

Had it accessed a non-static property or local variable, it would have created a closed delegate from a closure, and it would still work.

Decreasing answered 30/11, 2011 at 16:17 Comment(0)
S
4

The fact that the property will be eagerly accessed is not special to method-group members in any way; it's a characteristic of member-expressions in general.

It's actually the lambda that's creating the special case: its body (and thus the property-access) will be deferred until the delegate is actually executed.

From the specification:

7.6.4 Member access

[...] A member-access is either of the form E.I or of the form E.I, where E is a primary-expression.

[...] if E is a property or indexer access, then the value of the property or indexer access is obtained (§7.1.1) and E is reclassified as a value.

Selfstarter answered 30/11, 2011 at 16:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.