第二十章:异步和文件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,在这种情况下,构造函数将在异步操作的对象可用之前完成。 但这是不可避免的。

上一篇:如何将Exchange 2007迁移到一台新的服务器并且保留原有服务器名


下一篇:python文件操作