第二十章:異步和文件I/O.(五)

沒有任何警報
兩個DisplayAlert方法中較簡單的方法返回一個Task對象。 它旨在向用戶顯示一些不需要響應的信息:

Task DisplayAlert (string title, string message, string cancel)

通常,即使它沒有返回任何信息,你也會想要使用這個更簡單的DisplayAlert方法,特別是如果你需要在它被解除後執行一些處理。 NothingAlert程序與前面的示例具有相同的XAML文件,但顯示了這個更簡單的警告框:

public partial class NothingAlertPage : ContentPage
{
    public NothingAlertPage()
    {
        InitializeComponent();
    }
    async void OnButtonClicked(object sender, EventArgs args)
    {
        label.Text = "Displaying alert box";
        await DisplayAlert("Simple Alert", "Click 'dismiss' to dismiss", "dismiss");
        label.Text = "Alert has been dismissed";
    }
}

await運算符左側沒有任何內容,因爲DisplayAlert的返回值是Task而不是Task ,並且不返回任何信息。
本書中使用這種簡單形式的DisplayAlert的第一個程序是第15章中的SetTimer程序。這是來自該程序的計時器回調方法(奇怪的名稱爲@switch變量,因此它不與switch關鍵字衝突):

bool OnTimerTick()
{
    if (@switch.IsToggled && DateTime.Now >= triggerTime)
    {
        @switch.IsToggled = false;
        DisplayAlert("Timer Alert",
                     "The '" + entry.Text + "' timer has elapsed", 
                     "OK");
    }
    return true;
}

DisplayAlert調用快速返回,並在顯示警告框時繼續執行該方法。 然後OnTimerTick方法返回true,再次調用第二個OnTimerTick。 幸運的是,Switch不再切換,因此程序不會再次嘗試調用DisplayAlert。 當警報被解除時,用戶可以再次與用戶界面交互,但是在其返回時不執行額外的代碼。
如果您想在警報框被解除後執行一些代碼,該怎麼辦? 嘗試在DisplayAlert前放置一個await運算符,並使用async關鍵字標識該方法:

// Will not compile!
async bool OnTimerTick()
{
    if (@switch.IsToggled && DateTime.Now >= triggerTime)
    {
        @switch.IsToggled = false;
        await DisplayAlert("Timer Alert",
                           "The '" + entry.Text + "' timer has elapsed", 
                           "OK");
        // Some code to execute after the alert box is dismissed.
    }
    return true;
}

但正如評論所說,這段代碼不會編譯。
爲什麼不?
當C#編譯器遇到await關鍵字時,它會構造代碼,以便OnTimerTick回調返回給它的調用者。 然後,當警報框被解除時,該方法的其餘部分將繼續執行。 但是,調用此回調的Device.StartTimer方法期望定時器回調返回一個布爾值以確定是否應該再次調用回調,並且C#編譯器無法構造返回布爾值的代碼,因爲它不知道是什麼 那布爾值應該是!
因此,包含await運算符的方法僅限於返回void,Task或Task 的類型。
事件處理程序通常具有void返回類型。 這就是Button的Clicked處理程序可以包含await運算符並使用async關鍵字標記的原因。 但是計時器回調方法返回一個bool,並且要在此方法中使用await,OnTimerTick方法的返回值必須是Task :

// Method compiles but Device.StartTimer does not!
async Task<bool> OnTimerTick()
{
    if (@switch.IsToggled && DateTime.Now >= triggerTime)
    {
        @switch.IsToggled = false;
        await DisplayAlert("Timer Alert",
                           "The '" + entry.Text + "' timer has elapsed", 
                           "OK");
    }
    return true;
}

此方法現在包含完全合法的可編譯代碼。當一個方法被定義爲返回Task 時,該方法的主體返回一個類型爲T的對象,編譯器完成剩下的工作。
但是,因爲該方法現在返回一個Task 對象,所以調用此方法的代碼必須使用await方法(或在Task對象上調用ContinueWith)以在方法完成執行時獲取布爾值。這是Device.StartTimer調用的問題,它不期望回調方法是異步的;它期望回調方法返回bool而不是Task 。
如果您確實想在SetTimer程序中關閉警報後執行某些代碼,則應該使用ContinueWith代碼。 await運算符非常有用,但它不是解決每個異步編程問題的靈丹妙藥。
await運算符只能在方法中使用,並且該方法的返回類型必須爲void,Task或Task 。而已。屬性的get訪問器不能使用await,並且它們不應該執行異步操作。構造函數不能使用await,因爲構造函數不是方法,也沒有返回類型。你不能在lock語句的主體中使用await。 C#5還禁止在try-catch-finally語句的catch或finally塊中使用await,但是C#6解除了這個限制。
這些限制對於構造者來說是最嚴重的。構造函數應該及時完成,因爲在構造函數完成之前,對類的實例無法真正完成任何操作。儘管構造函數可以調用返回Task的異步方法,但構造函數不能對該調用使用await。構造函數在異步方法仍在處理時完成。 (您將在本章和下一章中看到一些示例。)
構造函數無法調用異步方法,該方法返回構造函數完成所需的值。 如果構造函數需要從異步操作中獲取對象,它可以使用ContinueWith,在這種情況下,構造函數將在異步操作的對象可用之前完成。 但這是不可避免的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章