При использовании метода C # группа выполняет код - PullRequest
14 голосов
/ 30 ноября 2011

При обновлении моего кода пользовательского интерфейса (C # в приложении .NET 4.0) я столкнулся со странным сбоем из-за вызова пользовательского интерфейса, выполняемого в неправильном потоке.Однако я уже вызывал этот вызов в основном потоке, поэтому сбой не имел смысла: MainThreadDispatcher.Invoke(new Action(View.Method)) аварийно завершился с «Вызывающим потоком не удалось получить доступ к этому объекту, потому что другой поток владеет им».в свойстве 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);
            }
        }
    }
}

Короткая версия:

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

Ответы [ 2 ]

6 голосов
/ 30 ноября 2011

Вы создаете закрытый делегат , который хранит объект this внутри делегата.(передать в качестве первого скрытого параметра в метод.)

Следовательно, при создании делегата из группы методов доступ к объекту осуществляется немедленно для сохранения в делегате.

В отличие от этого, когда вы создаете лямбда-выражение, доступ к объекту, которому принадлежит делегат, осуществляется только при вызове делегата.
Ваши лямбда-выражения создают открытый делегат, который обращается к свойству static непосредственно внутриделегат.

Если бы он получил доступ к нестатическому свойству или локальной переменной, он создал бы закрытый делегат из замыкания , и он все равно работал бы.

4 голосов
/ 30 ноября 2011

Тот факт, что свойство будет активно доступно, не является особенным для членов группы методов;это характерно для выражений членов в целом.

На самом деле лямбда создает особый случай: его тело (и, следовательно, доступ к свойству) будет отложено до фактического выполнения делегата.

Из спецификации:

7.6.4 Доступ к члену

[...] Доступ к члену имеет либо форму EI, либо форму EI, где E - первичное выражение.

[...] если E является доступом к свойству или индексатору, то получается значение доступа к свойству или индексатору (§7.1.1) и E переклассифицируется как значение.

...