Использование группы методов C# выполняет код

Обновляя код пользовательского интерфейса (C# в приложении .NET 4.0), я столкнулся со странным сбоем из-за того, что вызов пользовательского интерфейса выполнялся не в том потоке. Однако я уже вызывал этот вызов в главном потоке, поэтому сбой не имел смысла: MainThreadDispatcher.Invoke(new Action(View.Method)) произошел сбой с сообщением "The calling thread cannot access this object because a other thread owns it." на свойстве View.

После дальнейшего расследования я нашел причину: Я вызывал через группу методов. Я думал, что использование группы методов или делегата/лямбды по сути одно и то же (см. также этот вопрос и этот вопрос). Вместо этого преобразование группы методов в делегат приводит к выполнению кода, проверяющего значение View. Это делается немедленно, т.е. на исходном (не пользовательском) потоке, который и вызвал сбой. Если вместо этого я использую лямбду, проверка свойства выполняется позже и, следовательно, в правильном потоке.

Это, мягко говоря, интересно. Есть ли в стандарте C# место, где это упоминается? Или это подразумевается из-за необходимости найти правильное преобразование?

Вот тестовая программа. Во-первых, прямым способом. Второй - в два шага, что лучше показывает, что происходит. Для дополнительного развлечения я затем изменяю Item после создания делегата.

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);
            }
        }
    }
}

14
задан Community 23 May 2017 в 11:55
поделиться