在 Xamarin.iOS 中使用 UI 线程
应用程序用户界面始终是单线程的,即使在多线程设备中也是如此 - 屏幕只有一种表示形式,并且对显示内容的任何更改都需要通过单个“接入点”进行协调。 这可以防止多个线程尝试同时更新同一像素(举例而言)。
代码应该只从主(或 UI)线程对用户界面控件进行更改。 在另一线程(例如回调或后台线程)上发生的任何 UI 更新都可能不会呈现到屏幕上,甚至可能会导致崩溃。
UI 线程执行
在视图中创建控件或处理用户启动的事件(例如触摸)时,代码已经在 UI 线程的上下文中执行。
如果代码在后台线程、任务或回调中执行,则它可能不会在主 UI 线程上执行。 在这种情况下,应该将代码包装在对 InvokeOnMainThread
或 BeginInvokeOnMainThread
的调用中,如下所示:
InvokeOnMainThread ( () => {
// manipulate UI controls
});
InvokeOnMainThread
方法是在 NSObject
上定义的,因此可以从任何 UIKit 对象(例如视图或视图控制器)上定义的方法内调用它。
调试 Xamarin.iOS 应用程序时,如果代码尝试从错误的线程访问 UI 控件,将会引发错误。 这可以帮助你使用 InvokeOnMainThread 方法跟踪并修复这些问题。 这种情况仅在调试时发生,并且不会在发布版本中引发错误。 错误消息如下所示:
后台线程示例
下面是一个尝试使用简单线程从后台线程访问用户界面控件(一个 UILabel
)的示例:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
label1.Text = "updated in thread"; // should NOT reference UILabel on background thread!
})).Start();
该代码在调试时将引发 UIKitThreadAccessException
。 若要解决此问题(并确保只能从主 UI 线程访问用户界面控件),请将引用 UI 控件的所有代码包装在 InvokeOnMainThread
表达式内,如下所示:
new System.Threading.Thread(new System.Threading.ThreadStart(() => {
InvokeOnMainThread (() => {
label1.Text = "updated in thread"; // this works!
});
})).Start();
对于本文档的其余示例,不需要使用此功能,但当应用发出网络请求、使用通知中心或其他需要在另一个线程上运行的完成处理程序的方法时,需要记住这一重要概念。
Async/Await 示例
使用 C# 5 async/await 关键字时不需要 InvokeOnMainThread
,因为当等待的任务完成时,该方法将在调用线程上继续。
此示例代码(等待 Delay 方法调用,纯粹出于演示目的)显示了在 UI 线程上调用的异步方法(它是 TouchUpInside 处理程序)。 由于包含方法是在 UI 线程上调用的,因此在后台线程上完成异步操作后,可以安全地调用 UI 操作(例如在 UILabel
上设置文本或显示 UIAlertView
)。
async partial void button2_TouchUpInside (UIButton sender)
{
textfield1.ResignFirstResponder ();
textfield2.ResignFirstResponder ();
textview1.ResignFirstResponder ();
label1.Text = "async method started";
await Task.Delay(1000); // example purpose only
label1.Text = "1 second passed";
await Task.Delay(2000);
label1.Text = "2 more seconds passed";
await Task.Delay(1000);
new UIAlertView("Async method complete", "This method",
null, "Cancel", null)
.Show();
label1.Text = "async method completed";
}
如果从后台线程(而不是主 UI 线程)调用异步方法,则仍需要 InvokeOnMainThread
。