がりらぼ

WindowsRuntimeの応援ブログ

OnSuspendingでRelayCommandを呼ぶと死ぬ

通常、ストアアプリのデータ保存場所として、重いデータ以外はOnSuspendingイベントハンドラで保存を行うべきです。

OnSuspendingはどこにあるかというと、App.xaml.csファイル内にイベントハンドラを受け取っているOnSuspendingメソッドがあるのでこの中の、 var deferral=e.SuspendingOperation.GetDeferral(); から deferral.Complete(); までの間に保存処理を行えば、7秒ぐらいまでなら保存を待ってくれるそうです。 実際にやってみたら7秒以上も待ってくれた。

では本当に待ってくれるのか試してみます。

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    var deferral = e.SuspendingOperation.GetDeferral();
    //TODO: アプリケーションの状態を保存してバックグラウンドの動作があれば停止します
    Debug.WriteLine("セーブ開始");
    
    await TestTaskAsync();
    deferral.Complete();
}

private Task TestTaskAsync()
{
    return Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(state => Debug.WriteLine("セーブ完了"));
}

非同期タスクが最後まで実行されると出力にセーブ完了と表示されるはずです。

f:id:garicchi:20140410220106p:plain

ちゃんとタスクが最後まで実行されていました。

ですが、ViewModelからMVVMlightToolkitのRelayCommandでやった場合は結果が変わります。

こんなViewModelを用意して

public class MainViewModel
{
    public RelayCommand SaveCommand { get; set; }

    public MainViewModel()
    {
        SaveCommand=new RelayCommand(async() =>
        {
            await TestTaskAsync();
        });   
    }
    private Task TestTaskAsync()
    {
        return Task.Delay(TimeSpan.FromSeconds(10)).ContinueWith(state =>Debug.WriteLine("セーブ完了"));
    }

    
}

OnSuspendingからCommandを呼び出すと

private async void OnSuspending(object sender, SuspendingEventArgs e)
{
    var viewModel = new MainViewModel();
    var deferral = e.SuspendingOperation.GetDeferral();
    //TODO: アプリケーションの状態を保存してバックグラウンドの動作があれば停止します
    Debug.WriteLine("セーブ開始");
    viewModel.SaveCommand.Execute(null);
    //await TestTaskAsync();
    deferral.Complete();
}

f:id:garicchi:20140410220357p:plain

タスクが最後まで実行されません。

RelayCommand内はasyncで非同期に実行することができますが、Executeをawaitできていなからか、同期的ではなく、Execute以降がExecuteが完了していなくても実行されちゃいます。

つまりViewModelでセーブ処理をRelayCommandで書くと、OnSuspendingでデータ書き込み途中のデータを保存しちゃって、次回から起動しなくなったりするわけですね。