使用C#方法组执行代码

发布于 2024-12-18 16:56:30 字数 4898 浏览 1 评论 0原文

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

经过进一步调查,我找到了原因:我是通过方法组调用的。我曾认为使用方法组或委托/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);
            }
        }
    }
}

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

掌心的温暖 2024-12-25 16:56:30

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

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

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

如果它访问非静态属性或局部变量,它将创建一个封闭委托 来自闭包,它仍然可以工作。

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.

浅浅 2024-12-25 16:56:30

无论如何,属性将被急切地访问这一事实对于方法组成员来说并不特殊;一般来说,这是成员表达式的一个特征。

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

从规格来看:

7.6.4 会员访问

[...] 成员访问要么采用 EI 形式,要么采用 EI 形式,其中 E 是主表达式。

[...] 如果 E 是属性或索引器访问,则该值
获得属性或索引器访问权限(第 7.1.1 节),并且 E 是
重新分类为一个值。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文