我在更新我的UI代码(.NET 4.0应用程序中的C#)时,遇到了一个奇怪的崩溃,因为调用UI的线程错误执行。然而,我已经在主线程上调用了该方法,所以这个崩溃没有任何意义:MainThreadDispatcher.Invoke(new Action(View.Method))
在View属性上发生 "The calling thread cannot access this object because a different thread owns it." 的错误。
进一步调查后,我找到了原因:我是通过方法组调用的。我曾认为使用方法组或委托/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);
}
}
}
}
this
);我能理解他为什么感到困惑。 - SLaks