14

UIコード(.NET 4.0アプリケーションのC#)を更新しているときに、間違ったスレッドで実行されているUIの呼び出しが原因で、奇妙なクラッシュが発生しました。ただし、すでにメインスレッドでその呼び出しを呼び出していたため、クラッシュは意味MainThreadDispatcher.Invoke(new Action(View.Method))がありませんでした。「別のスレッドがオブジェクトを所有しているため、呼び出し元のスレッドはこのオブジェクトにアクセスできません」とクラッシュしました。Viewプロパティで。

さらに調査したところ、原因が見つかりました。メソッドグループを介して呼び出していました。メソッドグループまたはデリゲート/ラムダを使用することは本質的に同じことだと思っていました(この質問この質問も参照してください)。代わりに、メソッドグループをデリゲートに変換すると、コードが実行され、の値がチェックされますView。これはすぐに実行されます。つまり、クラッシュの原因となった元の(UI以外の)スレッドで実行されます。代わりにラムダを使用する場合、プロパティのチェックは後で行われるため、正しいスレッドで行われます。

控えめに言っても、それは面白そうです。これが言及されているC#標準の場所はありますか?それとも、正しい変換を見つける必要があるため、それは暗黙的ですか?

これがテストプログラムです。まず、直接的な方法です。次に、2つのステップで、何が起こるかをよりよく示します。さらに楽しくするため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(隠しパラメータとしてメソッドに渡します。)

したがって、メソッド グループからデリゲートを作成すると、オブジェクトはすぐにアクセスされ、デリゲートに格納されます。

対照的に、ラムダ式を作成すると、デリゲートを所有するオブジェクトは、デリゲートが呼び出されたときにのみアクセスされます。
ラムダ式は、staticデリゲート内でプロパティに直接アクセスするオープン デリゲートを作成します。

非静的プロパティまたはローカル変数にアクセスした場合、クロージャから閉じたデリゲートが作成され、引き続き機能します。

于 2011-11-30T16:17:48.340 に答える
4

プロパティが頻繁にアクセスされるという事実は、メソッド グループ メンバーにとって特別なことではありません。これは一般的なメンバー式の特徴です。

特殊なケースを作成しているのは実際にはラムダです。デリゲートが実際に実行されるまで、その本体 (およびプロパティ アクセス) は延期されます。

仕様から:

7.6.4 メンバー アクセス

[...] メンバー アクセスは、EI の形式または EI の形式のいずれかです。ここで、E はプライマリ式です。

[...] E がプロパティまたはインデクサー アクセスの場合、プロパティまたはインデクサー アクセスの値が取得され (§7.1.1)、E は値として再分類されます。

于 2011-11-30T16:17:29.497 に答える