刚接触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); });