14

在更新我的 UI 代码(.NET 4.0 应用程序中的 C#)时,由于在错误线程中执行对 UI 的调用,我遇到了奇怪的崩溃。但是,我已经在主线程上调用了该调用,因此崩溃没有任何意义:MainThreadDispatcher.Invoke(new Action(View.Method))因“调用线程无法访问此对象,因为不同的线程拥有它”而崩溃。在 View 属性上。

经过进一步调查,我找到了原因:我正在通过方法组调用。我曾认为使用方法组或委托/lambda 本质上是相同的(另请参见这个问题这个问题)。相反,将方法组转换为委托会导致代码执行,检查View. 这是立即完成的,即在导致崩溃的原始(非 UI)线程上。如果我改用 lambda,则稍后会在正确的线程中检查属性。

至少可以这么说,这似乎很有趣。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);
            }
        }
    }
}
4

2 回答 2

6

您正在创建一个封闭的委托,它将this对象存储在委托中。(作为隐藏的第一个参数传递给方法。)

因此,当您从方法组创建委托时,会立即访问对象以存储在委托中。

相比之下,当您创建 lambda 表达式时,只有在调用委托时才能访问拥有该委托的对象。
您的 lambda 表达式会创建一个开放委托,该委托可以static直接在委托中访问该属性。

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.

于 2011-11-30T16:17:48.340 回答
4

属性将被热切访问的事实对于方法组成员来说并不是特别的。这是一般成员表达式的一个特征。

实际上是 lambda 创建了特殊情况:它的主体(以及属性访问)将被推迟到实际执行委托为止。

从规范:

7.6.4 会员访问

[...] 成员访问是 EI 形式或 EI 形式,其中 E 是主表达式。

[...]如果 E 是属性或索引器访问,则获取属性或索引器访问的值(第 7.1.1 节)并将 E 重新分类为值。

于 2011-11-30T16:17:29.497 回答