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