第二十章:异步和文件I/O.(六)

异步保存程序设置
正如您在第6章“按钮单击”中发现的那样,您可以将程序设置保存在Application类维护的名为Properties的字典中。您在“属性”字典中放置的任何内容都将在程序进入睡眠状态时保存,并在程序恢复或重新启动时恢复。有时在更改时保存此字典中的设置很方便,有时候等到在App类中调用OnSleep方法很方便。
还有另一种选择:Application类有一个名为SavePropertiesAsync的方法,它允许您的程序在保存程序设置时发挥更积极的作用。这允许程序随时保存程序设置。如果程序稍后崩溃或通过Visual Studio或Xamarin Studio调试程序终止,则会保存设置。
根据建议的做法,SavePropertiesAsync方法名称上的Async后缀将此标识为异步方法。它使用Task对象快速返回,并将设置保存在辅助执行线程中。
名为SaveProgramSettings的程序演示了这种技术。 XAML文件包含四个Switch视图和四个Label视图,将Switch视图视为二进制数字的数字:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit=
                 "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="SaveProgramSettings.SaveProgramSettingsPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <toolkit:BoolToStringConverter x:Key="boolToString"
                                           FalseText="Zero"
                                           TrueText="One" />
            <Style TargetType="Label">
                <Setter Property="FontSize" Value="Large" />
                <Setter Property="HorizontalTextAlignment" Value="Center" />
            </Style>
            <Style TargetType="Switch">
                <Setter Property="HorizontalOptions" Value="Center" />
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <Grid VerticalOptions="CenterAndExpand">
            <Label Text="{Binding Source={x:Reference s3}, 
                                  Path=IsToggled,
                                  Converter={StaticResource boolToString}"
                   Grid.Column="0" />
 
            <Label Text="{Binding Source={x:Reference s2}, 
                                  Path=IsToggled,
                                  Converter={StaticResource boolToString}"
                   Grid.Column="1" />
 
            <Label Text="{Binding Source={x:Reference s1}, 
                                  Path=IsToggled,
                                  Converter={StaticResource boolToString}"
                   Grid.Column="2" />
 
            <Label Text="{Binding Source={x:Reference s0}, 
                                  Path=IsToggled,
                                  Converter={StaticResource boolToString}"
                   Grid.Column="3" />
        </Grid>
        <Grid x:Name="switchGrid"
              VerticalOptions="CenterAndExpand">
            <Switch x:Name="s3" Grid.Column="0"
                    Toggled="OnSwitchToggled" />
 
            <Switch x:Name="s2" Grid.Column="1"
                    Toggled="OnSwitchToggled" />
            <Switch x:Name="s1" Grid.Column="2"
                    Toggled="OnSwitchToggled" />
            <Switch x:Name="s0" Grid.Column="3"
                    Toggled="OnSwitchToggled" />
        </Grid>
    </StackLayout>
</ContentPage> 

Label元素上的数据绑定允许它们跟踪Switch视图的值:
2018_11_18_194632
保存和检索程序设置在代码隐藏文件中处理。 注意分配给Switch元素的Toggled事件的处理程序。 该处理程序的唯一目的是将设置存储在属性字典中 - 并且只要其中一个Switch元素更改状态,就可以使用SavePropertiesAsync保存属性字典本身。 字典键是Grid的Children集合中Switch的索引:

public partial class SaveProgramSettingsPage : ContentPage
{
    bool isInitialized = false;
    public SaveProgramSettingsPage()
    {
        InitializeComponent();
        // Retrieve settings.
        IDictionary<string, object> properties = Application.Current.Properties;
        for (int index = 0; index < 4; index++)
        {
            Switch switcher = (Switch)(switchGrid.Children[index]);
            string key = index.ToString();
            if (properties.ContainsKey(key))
                switcher.IsToggled = (bool)(properties[key]);
        }
        isInitialized = true;
    }
    async void OnSwitchToggled(object sender, EventArgs args)
    {
        if (!isInitialized)
            return;
        Switch switcher = (Switch)sender;
        string key = switchGrid.Children.IndexOf(switcher).ToString();
        Application.Current.Properties[key] = switcher.IsToggled;
        // Save settings.
        foreach (View view in switchGrid.Children)
            view.IsEnabled = false;
        await Application.Current.SavePropertiesAsync();
        foreach (View view in switchGrid.Children)
            view.IsEnabled = true;
    }
}

本练习的目的之一是首先强调,使用await并不能完全解决异步操作所涉及的问题,其次,使用await可以帮助解决这些潜在的问题。
这是问题:每次Switch改变状态时都会调用Toggled事件处理程序。可能是用户很快就连续切换了几个Switch视图。也可能是SavePropertiesAsync方法很慢的情况。也许它比四个布尔值节省了更多的信息。因为这种方法是异步的,所以当它仍在努力保存以前的设置集合时,可能会再次调用它。
SavePropertiesAsync是否可以重入?当它仍在工作时能否安全地再次调用它?我们不知道,最好假设它不是。因此,处理程序在调用SavePropertiesAsync之前禁用所有Switch元素,然后在完成后重新启用它们。因为SavePropertiesAsync返回Task而不是Task ,所以没有必要使用await(或ContinueWith)从方法中获取值,但是如果要在方法完成后执行某些代码,则必须使用它。
实际上,SavePropertiesAsync在这种情况下运行得如此之快,以至于很难判断这种禁用和启用Switch视图是否正常工作!为了测试这样的代码,Task类的静态方法非常有用。尝试在SavePropertiesAsync调用之后插入此语句:

await Task.Delay(3000);

Switch元素被禁用另外3,000毫秒。 当然,如果异步操作确实需要很长时间才能完成,并且在此期间禁用了用户界面,则可能需要显示ActivityIndicator或ProgressBar。
Task.Delay方法可能看起来让人想起许多年前在某些.NET代码中可能使用的Thread.Sleep方法。 但是这两种静态方法是非常不同的。 Thread.Sleep方法挂起当前线程,在这种情况下,它将是用户界面线程。 这正是你不想要的。 但是,Task.Delay调用模拟在指定时间段内运行的无操作辅助线程。 用户界面线程未被阻止。 如果省略await运算符,Task.Delay似乎根本不会对程序产生任何影响。 与await运算符一起使用时,调用Task.Delay的方法中的代码将在指定的时间段后恢复。

猜你喜欢

转载自yq.aliyun.com/articles/670683
今日推荐