刚接触xamarin, 就碰到了一个巨坑:在执行耗时任务时,主线程被阻塞,界面被锁死,即使在执行任务前,给了ActivityIndicatior.IsRunning=true都不显示系统忙的状态。于是花了一个多星期在浩瀚的海洋里寻找各种解决方法,终于在巨硬的一篇xamarin.ios的一篇文档中找到了参考:使用 Xamarin 中的 UI 线程 - Xamarin | Microsoft Docs, 文档用Thread和Task/Await实现了主线程和工作线程的互动,虽然这篇文档是写给IOS的,但实际上是关于线程Thread, Task/Await的,其他平台也一样适用。
我稍微修改了一下,在android上也实现了。几个关键的例程记录一下:
1. 耗时方法,写成同步方式
private static List<string> ScanDevice(byte[] sn) { List<string> list = new List<string>(); for (int i = 0; i < 4; i++) { Thread.Sleep(2000); list.Add("SerialNum " + i); } return list; }
2. 主线程更新UI方法
void UpdateUiState(bool isTaskRunning) { runningStatusLabel.Text = isTaskRunning ? "A task is in progress." : "All tasks complete!"; defaultActivityIndicator.IsRunning = isTaskRunning; }
这里更新了一个Lable控件和一个ActivityIndicator控件
3.Thread线程开启工作线程,并在工作线程中更新UI
void OnButtonClicked(object sender, EventArgs e) {// 可以更新UI UpdateUiState(true);//工作状态指示 new Thread(new ThreadStart(() => {//开启工作线程并开始耗时工作 var devices = ScanDevice(null); var text = "Devices = " + devices.Count; Device.InvokeOnMainThreadAsync(() => {//工作完成,更新UI界面 UpdateUiState(false); DisplayAlert("info", text, "OK"); }); })).Start(); }
在工作线程中启用了Device.InvokeOnMainThreadAsync()操作线程。
4.Task/Await与UI互动,在await前后更新UI即可
List<string> mDevices;//用一个全局变量接收工作结果 async private void OnTaskDemoClicked(object sender, EventArgs e) {//标记为异步方法 UpdateUiState(true);//工作开始指示 await Task.Run(() => {//Task/Await方式,不需要在在子线程更新UI线程,但用了也没问题 //Device.InvokeOnMainThreadAsync(() => UpdateUiState(true)); mDevices = ScanDevice(null); }); UpdateUiState(false);//工作结束指示 var text = "Devices = " + mDevices.Count; await DisplayAlert("info", text, "OK"); }
我的理解,加了Await之后,程序在等待异步方法完成,也就异步方法变成了同步方法,UI的更新自然也就可以顺序执行了。
我才疏学浅,刚开始接触Thread/Task/Await,很是晕菜,有个缺陷还没解决:耗时工作方法中的返回值,在异步执行中我不知道怎么接收,所以用了全局变量来接收。如果有知道的高手请留言告诉我,万分感谢。
更新:“有个缺陷还没解决:耗时工作方法中的返回值,在异步执行中我不知道怎么接收”这个问题已经解决:
var devices = await Task.Run(()=> { return ScanDevice(null); });