- 學會非同步寫法...
圖形使用者介面 (GUI) 有一個特質,執行事件時都是 有順序性 的,每一件事情執行前都要等待另一件事情結束。
一開始,所有程式都會連結到一條執行緒內動作,這條執行緒也稱做 main thread、user-interface thread 或 UI thread
但若 main thread 因為某個大量工作事件卡住,導致介面呈現 假當機 狀態,會讓使用者森氣氣的…
為了改進使用者經驗,我們開始想辦法將這些會卡住的大量工作,丟到其他執行緒去處理而不影響 UI thread 的執行,這些執行緒也稱做 work thread
可以把他們想像成在背景執行的工作,不會影響 UI thread 的運作。
近幾年來,非同步其實越來越好寫了,更幸運的是,在 C# 5.0 後支援 async/await,讓非同步寫法 超級簡化。
底下先來介紹 Xamarin.Forms 內建的非同步功能 - DiplayAlert,同時帶著大家了解 async/await 的寫法
前面章節其實已有出現過一些非同步範例,
如 13 章 的 ImageBrowser 和 BitmapStreams,或是 19 章 RssFeed 內的 WebRequest 所搭配的 BeginGetResponse、EndGetResponse。
這邊只會介紹 async 和 await 的非同步寫法,
Xamarin.Forms 另有提供 Device.BeginInvokeOnMainThread 方法就不贅述,功用大同小異,若有興趣可以看原文書 p.626 An alert with callbacks
DiplayAlert
[alert 視窗] (或稱 message box) 通常拿來 提醒使用者 或是 詢問問題,
當 [alert 視窗] 顯示時,App 其他功能都是沒辦法動作的,一定要等到 [alert 視窗] 消失,使用者才能再次與應用程式互動。
因為上面描述的特性,Xamarin 將之定義成非同步方法,讓視窗彈出來時,主程式還是能繼續執行。
page 類別內定義了三個方法彈出 [alert 視窗]:
- Task DisplayAlert (string title, string message, string cancel)
- Task<bool> DisplayAlert (string title, string message, string accept, string cancel)
- Task<string> DisplayActionSheet (string title, string cancel, string destruction, params string[] buttons)
差異在...
第一個只會顯示 一個 解除按鈕,第二個會顯示 是或否 兩個 按鈕,第三個方法則可以自訂 多個 按鈕...
備註:
這三個方法都是回傳 Task/Task<T> 物件,設計概念來自 Tak-based Asynchronous Pattern (TAP),TAP 也是.NET 比較推薦的非同步做法。
之前範例用 WebRequest 的 BeginGetResponse 和 EndGetResponse 方法,是屬於比較舊的非同步方法叫做 Asynchronous Programming Model (APM)。
底下來看範例,
功能很簡單,彈出一個 [alert 視窗] 並詢問使用者,當使用者按下 yes 時回傳 true,反之回傳 false,等關閉視窗後再更改 Label 的文字
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="AlertCallbacks.AlertCallbacksPage"> <StackLayout> <Button Text="Invoke Alert" FontSize="Large" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" Clicked="OnButtonClicked" /> <Label x:Name="label" Text="Tap button to invoke alert" FontSize="Large" HorizontalTextAlignment="Center" VerticalOptions="CenterAndExpand" /> </StackLayout> </ContentPage>
.cs:
public partial class AlertAwaitPage : ContentPage { public AlertAwaitPage() { InitializeComponent(); } async void OnButtonClicked(object sender, EventArgs args) { Tasktask = DisplayAlert("Simple Alert", "Decide on an option", "yes or ok", "no or cancel"); label.Text = "Alert is currently displayed"; bool result = await task; label.Text = String.Format("Alert {0} button was pressed", result ? "OK" : "Cancel"); } }
關鍵就是 16 行
bool result = await task;
當我們使用 DisplayAlert 時,回傳的類別應該是 Task<bool>,但 await 這關鍵字似乎很神奇地幫我們將 bool 抽離出來!!
執行的步驟大家可以跟著文字想像一下:
1. 當 Button 按下去
執行到 DisplayAlert 會丟出一條 [彈出 alert 視窗] thread,
但系統會繼續往下執行程式:Label.Text = "Alert is currently displayed"
2. 遇到關鍵 16 行 bool result = await task;
result 等待視窗回傳結果,但系統還是繼續執行其他和 task 無關 的程式碼
3. 等到視窗關閉回傳結果
系統再回來執行跟 task 有關 的程式碼:Label.Text = result ? "OK" :"Cancel"
相當聰明且很好寫…感謝 .NET 讚嘆 .NET...
*使用 await 時,function 指示詞一定要設定為 async
也可以將以上程式碼合併成一行這樣:
async void OnButtonClicked(object sender, EventArgs args) { label.Text = String.Format("Alert {0} button was pressed", await DisplayAlert("Simple Alert", "Decide on an option", "yes or ok", "no or cancel") ? "OK" : "Cancel"); }
*酷炫
小練習
練習寫一個非同步的 function 並呼叫,
功能很簡單,顯示目前時間並隨機放置顯示的位置:
XAML:
*OnPlatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TaskDelayClock.TaskDelayClockPage"> <ContentPage.Padding> <OnPlatform x:TypeArguments="Thickness" iOS="0, 20, 0, 0" /> </ContentPage.Padding> <AbsoluteLayout> <Label x:Name="label" FontSize="Large" AbsoluteLayout.LayoutFlags="PositionProportional" /> </AbsoluteLayout> </ContentPage>
.cs
public partial class TaskDelayClockPage : ContentPage { Random random = new Random(); public TaskDelayClockPage() { InitializeComponent(); InfiniteLoop(); } async void InfiniteLoop() { while (true) { label.Text = DateTime.Now.ToString("T"); label.FontSize = random.Next(12, 49); AbsoluteLayout.SetLayoutBounds(label, new Rectangle(random.NextDouble(), random.NextDouble(), AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize)); //將 Task 暫停 0.25 秒 await Task.Delay(250); } } }
為了不卡 main thread,這邊的 無窮迴圈 寫成非同步方法,
並利用 await Task.Delay(250),讓 Label 每隔 0.25 秒顯示當時時間並換位置...
執行結果:
*這邊給個建議,若是要寫非同步方法,方法命名上可以在最後加上 Async,
舉上面的例子來說,可以改命名為 InfiniteLoopAsync(),讓程式更好維護的啦
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。