第二十章:非同步和檔案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檢視的值:
儲存和檢索程式設定在程式碼隱藏檔案中處理。 注意分配給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的方法中的程式碼將在指定的時間段後恢復。