読者です 読者をやめる 読者になる 読者になる

がりらぼ

WindowsRuntimeの応援ブログ

awaitできるRelayCommandを考える

前回、OnSuspendingでRelayCommandをやると死ぬという記事を書きましたが、 なぜ死ぬか、完結にまとめると、

・RelayCommandのExecuteメソッドはvoid型を返す

・RelayCommand内に登録するActionをasyncにするとそこは非同期になるけどExecuteがTaskを返さないのでExecuteを実行した下の行以降は非同期で実行されてしまう。

・OnSuspendingで実行するとExecute以下が非同期のまま実行されて、データが保存されずにアプリが終了してしまう

ということっぽいです。

じゃあどうするのってとこで、ExecuteがTaskを返してActionを実行すればいい。 拡張メソッドも考えたけどRelayCommandはジェネリッククラスなので実装できない。

というわけでRelayCommandを継承してExecuteAsyncというメソッドを生やすことで解決。

public class AwaitableRelayCommand<T>:RelayCommand<T>
{
    public AwaitableRelayCommand(Action<T> execute) : base(execute)
    {
    }

    public AwaitableRelayCommand(Action<T> execute, Func<T, bool> canExecute) : base(execute, canExecute)
    {
    }

    public Task ExecuteAsync(object parameter)
    {
        return Task.Run(() => this.Execute(parameter));
    }
}

public class AwaitableRelayCommand : RelayCommand
{
    public AwaitableRelayCommand(Action execute)
        : base(execute)
    {
    }

    public AwaitableRelayCommand(Action execute, Func<bool> canExecute)
        : base(execute, canExecute)
    {
    }

    public Task ExecuteAsync(object parameter)
    {
        return Task.Run(() => this.Execute(parameter));
    }
}

ExecuteAsyncメソッドを作って、中でExecuteメソッドをTask.Runで非同期実行します。 RelayCommandのジェネリッククラスと非ジェネリッククラスの2つがあると便利だったと思う。

これで前回のコードを実行するときに、 await viewModel.ExecuteAsync(null); としてやるとちゃんとデータを保存できる。

f:id:garicchi:20140410220106p:plain

このやり方はXAMLのCommandプロパティとかInvokeCommandBehaviorからは無理だと思う。 とりあえず、絶対viewModelでしかもCommandでデータ保存を実装したいんだ!! って人むけでおすすめ。 コードビハインドからしか実行できないと思うので微妙な感じはあるけれど、 AwaitableRelayCommandを使うと別に後々の処理を待たなくても良い場合はXAMLからExecuteを呼べばいいし、後々の処理を待ちたい場合はコードビハインドからExecuteAsyncを呼べば同じActionを実行できる。