2011-11-30 10 views
14

UIコード(.NET 4.0アプリケーションではC#)を更新している間に、間違ったスレッドでUIが呼び出されたために異常なクラッシュが発生しました。しかし、私はすでにメインスレッドでその呼び出しを呼び出していたので、クラッシュは意味をなさない。MainThreadDispatcher.Invoke(new Action(View.Method))が "別のスレッドがそれを所有しているため、このオブジェクトにアクセスすることができない"とクラッシュした。 Viewプロパティで設定します。C#メソッドグループを使用してコードを実行します

さらに調査したところ、私は原因を見つけました:私はメソッドグループ経由で呼び出していました。メソッドグループやデリゲート/ラムダを使用することは本質的に同じことと考えていました(this questionthis questionも参照)。代わりに、メソッドグループを代理人に変換すると、コードが実行され、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); 
      } 
     } 
    } 
} 
+2

どの呼び出しが失敗しますか?コールスタックとは何ですか? – SLaks

+0

VerifyAccess()でコメント行を削除すると、Itemプロパティが子スレッドでアクセスされるため、メソッドグループを使用するすべての呼び出しが失敗することがわかります。 –

+1

問題のあるコールを表示する、短くて完全なプログラム*ちょっとしたことが本当に役に立ちます。 –

答えて

4

性が熱心にアクセスされるという事実は、任意の方法でメソッドグループメンバーに特別ではありません。それは一般的なメンバー表現の特徴です。

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

7.6.4メンバーアクセス

[...]メンバアクセスは、フォームのいずれかEI以上であるEはフォームEI、の一次発現。

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

6

あなたは、デリゲートの内部thisオブジェクトを格納closed delegateを、作成しています。 (方法に隠された最初のパラメータとして渡す。)

をしたがって、メソッドグループからデリゲートを作成するときに、オブジェクトがデリゲートに格納するために直ちにアクセスされます。あなたはラムダ式を作成するときに、デリゲートが呼び出されたときに、これとは対照的に

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

非静的なプロパティまたはローカル変数にアクセスした場合、クローズドデリゲートfrom a closureが作成され、それでも機能します。

関連する問題