ReactiveProperty 5.2.0の新機能Awaitableを使ってみた


概要

ReactiveProeprty 5.2.0の新機能AwaitableなReactivePropertyを使ってみました。

ReactivePropertyの説明自体は下記を参照ください。

https://blog.okazuki.jp/entry/2015/12/05/221154


AwaitableなReactivePropertyとは

ReactivePropertyがAwaitable パターンを実装したことで、awaitで値の変更を待機することができるようになりました。

今までもSubscribeで値の変更を購読することはできましたが、


  • 1度だけ購読して何かしたい

  • 値の変更を待機して、次の処理を続けて書きたい

といった時には少々手間でした。

Awaitableパターンが実装されたことにより、下記のような形で値の変更を待機することができます。

var newValue = await MyReactiveProperty;

他にもSubscribeとの相違点としては、ReactivePropertyをSubscribeするとModeがRaiseLatestValueOnSubscribeならばすぐに値が返ってきますが、

awaitした場合は、Modeに関係なく変更するまで待機します。

なお、Awaitableになったのは狭義のReactivePropertyだけでなく、

(ReadOnly)ReactiveProperty(Slim)と(Async)ReactiveCommand(Generic)もです。

つまりだいたい全部です。

ReactiveCommandの場合はCanExcuteの変化ではなく、Excute呼び出しを待機して、返り値はExcute呼び出し時の引数です。

注意点として、今回のバージョンには破壊的変更が含まれています。

あまり実用的ではないのですが、5.1以前でもawaitで待機することは可能でした。

この場合はReactivePropertyがDisposeされるまで待機して、最後の値が返ってきます。

これはReactivePropertyというよりも、IObsevableの機能です。


デモ

デモプログラムを作ってみました。

WPFでファイルの変更を監視して、それをTextBoxに表示するアプリケーションを作ります。

ボタン押下後に監視を開始して、変更されたら1度だけTextBoxに反映させます。


デモ実行結果

起動時

スクリーンショット 2018-08-02 22.18.07.png

Button押下後、awaitで待機中はボタンが無効になっています。

スクリーンショット 2018-08-02 22.15.47.png

メモ帳で監視対象のファイルを変更します。

スクリーンショット 2018-08-02 22.16.13.png

TextBoxに入力された文字が大文字化されて入ります。以降はファイルを変更しても反映されません。

image.png


ViewModel

ViewModelではAsyncCommandを用意して、コマンドのSubscribe内でmodelのReactivePropertySlimの変更をawaitで待機しています。

待機している間、コマンドのCanExcuteはfalseになります。

ReactivePropertySlimが変更されたら、その値を大文字化して、VM内のReactivePropertyに反映させます。


MainWindowViewModel.cs

class MainWindowViewModel : INotifyPropertyChanged

{
public event PropertyChangedEventHandler PropertyChanged;

private Model model = new Model();

public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>("TARO");

public AsyncReactiveCommand UpdateWaitLoadCommand { get; } = new AsyncReactiveCommand();

public MainWindowViewModel()
{
UpdateWaitLoadCommand.Subscribe(DoSomeAsync);
}

private async Task DoSomeAsync()
{
model.StartLoad();
string loadText = await model.LoadedText; //ReactivePropertyの変更を待つ

Name.Value = loadText.ToUpper();
}
}



Model

Modelでは一定時間ごとにファイルを見て、Model内のReactivePropertySlimに反映しています。


Model.cs

class Model

{
public ReactivePropertySlim<string> LoadedText { get; } = new ReactivePropertySlim<string>("");

private const string inputPath = "inputText.txt";
private Timer timerLoad = new Timer();

public Model()
{
timerLoad.Elapsed += TimerLoad_Elapsed;
}

private void TimerLoad_Elapsed(object sender, ElapsedEventArgs e)
{
string loadedText = File.ReadAllText(inputPath);
LoadedText.Value = loadedText;
}

/// <summary>
/// ファイル監視の開始
/// </summary>
internal void StartLoad()
{
using (File.Create(inputPath)) { }

timerLoad.Start();
}
}



View

ViewではVMのReactivePropertyとAsyncReactiveCommandにBindingしています。


MainWindow.xaml

<Window

x:Class="TestReactiveProperty52.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestReactiveProperty52"
Width="300"
Height="150">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Name.Value, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding UpdateWaitLoadCommand}" Content="ファイルが変更されるのを待って反映" />
</StackPanel>
</Window>


参考

https://runceel.github.io/ReactiveProperty/advanced/awaitable/#awaitable

https://ufcpp.net/study/csharp/sp5_awaitable.html


環境

VisualStudio2017

.NET Framework 4.7.1

C#7.1

ReactiveProperty 5.2.0