ReactiveUIの抂芁孊習コマンド

パヌト1ReactiveUIの抂芁ViewModelのプロパティをポンピングする

パヌト2ReactiveUIの抂芁コレクション



プロパティの操䜜、プロパティ間の䟝存関係の構築、コレクションの操䜜に関連するReactiveUIの機胜に぀いおは既に説明したした。 これらは、ReactiveUIを䜿甚した開発の基瀎ずなる基本的なプリミティブの1぀です。 別のそのようなプリミティブは、この郚分で怜蚎するチヌムです。 チヌムは、䜕らかのむベントに応じお実行されるアクションをカプセル化したす。通垞、これはナヌザヌリク゚ストたたは䜕らかの远跡された倉曎です。 ReactiveUIのコマンドを䜿甚しお䜕ができるかを孊習し、それらの䜜業の機胜に぀いお説明し、ReactiveUIのコマンドがWPFおよびその芪fromから粟通しおいるチヌムずどのように異なるかを調べたす。

ただし、コマンドに移る前に、リアクティブプログラミングに関するより広範なトピック、぀たりタスク<T>ずIObservable <T>の関係、およびホットシヌケンスずコヌルドシヌケンスに぀いお説明したす。



タスク察 IObservable



したがっお、タスク<T>+ async、await、それだけずIObservable <T>の間に類䌌点を描きたしょう。 ReactiveUIでチヌムず連携する方法を理解するこずは重芁ですが、説明したアプロヌチはより広範なアプリケヌションを持ち、それに぀いお知るのに害はありたせん。 したがっお、 タスク<T>はIObservable <T>です。 しかし、それらは確かに同等ではありたせん。IObservable<T>ははるかに広範なタスクを解決できたす。

なんずなく怪しいですね。 正しくしたしょう。 すぐに䟋を芋おみたしょう

Task<string> task = Task.Run(() => { Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); Thread.Sleep(1000); Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); return "  "; }); Console.WriteLine(DateTime.Now.ToLongTimeString() + "  -     "); string result = task.Result; Console.WriteLine(DateTime.Now.ToLongTimeString() + "  : " + result);
      
      





タスクを䜜成したした。非同期に実行され、起動埌すぐに完了を埅たずに他のこずを行うこずを劚げたせん。 結果は予枬可胜です

18:19:47タスクの結果を埅぀前に䜕かをする

18:19:47長いタスクを開始する

18:19:48長いタスクを完了したす

18:19:48結果長いタスクの結果



最初の2行はすぐに衚瀺され、ラッキヌずしお別の順序になる堎合がありたす。

そしお、IObservable <T>で曞き換えたす

 IObservable<string> task = Observable.Start(() => { Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); Thread.Sleep(1000); Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); return "  "; }); Console.WriteLine(DateTime.Now.ToLongTimeString() + "  -     "); string result = task.Wait(); //       Console.WriteLine(DateTime.Now.ToLongTimeString() + "  : " + result);
      
      





違いは2行ですTask <string>の代わりにIObservable <string>、Task.Runの代わりにObservable.Start、task.Resultの代わりにtask.Wait。 䜜業の結果はたったく同じです。



タスクの完了埌にアクションを開始する別の有名なトリックを芋おみたしょう。

 //Task task.ContinueWith(t => Console.WriteLine(DateTime.Now.ToLongTimeString() + "  : " + t.Result)); //IObservable task.Subscribe(t => Console.WriteLine(DateTime.Now.ToLongTimeString() + "  : " + t));
      
      





ここでも、実質的に違いはありたせん。



タスク<T>はIObservable <T>で衚すこずができ、1぀の芁玠ず完了信号を生成したす。 このようなアプロヌチの間に哲孊的およびアヌキテクチャ䞊の倧きな違いはなく、誰でも䜿甚できたす。 async / awaitでも䞡方の堎合に利甚できたす。非同期メ゜ッドでIObservableから結果を取埗する堎合、䟋のようにWaitメ゜ッドで埅機をブロックするこずはできたせんが、 awaitを䜿甚したす。 さらに、これら2぀のアプロヌチを組み合わせお、1぀のビュヌを別のビュヌに倉換し、䞡方の利点を掻甚できたす。



ホットシヌケンスずコヌルドシヌケンス



オブザヌバブルシヌケンスオブザヌバブルを䜿甚した䜜業に関するもう1぀の質問に぀いお説明したす。 コヌルドコヌルドずホットホットの2぀のタむプがありたす。 コヌルドシヌケンスは受動的であり、芁求に応じお、サブスクラむブ時に通知を生成し始めたす。 ホットシヌケンスはアクティブであり、誰かがサブスクラむブしたかどうかに䟝存したせん。通知はずにかく生成されたすが、たたに䜕も送信されたせん。

タむマヌティック、マりス移動むベント、ネットワヌク経由のリク゚ストはホットシヌケンスです。 ある時点でそれらにサブスクラむブするこずにより、珟圚の通知を受信し始めたす。 10人のオブザヌバヌが賌読したす-通知は党員に配信されたす。 タむマヌの䟋を次に瀺したす。





コヌルドシヌケンスずは、たずえば、デヌタベヌスぞのク゚リや、ファむルを1行ず぀読み取るこずです。 サブスクリプション時に芁求たたは読み取りが開始され、新しい行が受信されるたびにOnNextが呌び出されたす。 行が終了するず、OnCompleteが呌び出されたす。 再サブスクラむブするず、すべおが再び繰り返されたす。デヌタベヌス内の新しいク゚リたたはファむルを開いお、すべおの結果を返し、完了シグナルを返したす。








クラシックチヌム...



それでは、今日のトピックであるチヌムに移りたしょう。 すでに述べたように、チヌムは特定のむベントに応じお実行されるアクションをカプセル化したす。 このようなむベントは、ナヌザヌが「保存」ボタンをクリックするこずです。 カプセル化されたアクションは、䜕かを保存する操䜜になりたす。 ただし、コマンドは、明瀺的なナヌザヌアクションたたは関連する間接むベントぞの応答ずしおだけでなく実行できたす。 ナヌザヌに関係なく、5分ごずに実行されるタむマヌからの信号も、同じ「保存」コマンドを開始できたす。 たた、コマンドは通垞、ナヌザヌが䜕らかの方法で実行するアクションに察しお正確に䜿甚されたすが、他のケヌスでの䜿甚を無芖しないでください。

たた、コマンドを䜿甚するず、実行が珟圚䜿甚可胜かどうかを確認できたす。 たずえば、保存を垞に䜿甚できるようにするのではなく、フォヌムのすべおの必須フィヌルドが入力され、むンタヌフェむスでボタンがアクティブかどうかはコマンドの可甚性に䟝存したす。

ICommandチヌムむンタヌフェむスずは䜕かを芋おみたしょう。

 public interface ICommand { event EventHandler CanExecuteChanged; bool CanExecute(object parameter); void Execute(object parameter); }
      
      





実行は明らかにコマンドを実行したす。 パラメヌタヌを枡すこずができたすが、必芁な倀がコマンド自䜓の内郚で取埗できる堎合たずえば、ViewModelから取埗する堎合、この機䌚を䜿甚しないでください。 さらにその理由を理解したす。 しかし、もちろん、パラメヌタヌを枡すこずが最も受け入れられるオプションである堎合がありたす。

CanExecuteは、コマンド実行が珟圚䜿甚可胜かどうかを確認したす。 パラメヌタヌもあり、ここではすべおがExecuteず同じです。 特定のパラメヌタヌ倀を䜿甚したCanExecuteが、同じパラメヌタヌ倀のみを䜿甚したコマンドの実行を蚱可たたは犁止するこずが重芁です。他の倀に぀いおは、結果が異なる堎合がありたす。 たた、Executeは、アクションを実行する前にCanExecuteをチェックせず、呌び出しコヌドのタスクであるこずに泚意しおください。

CanExecuteChangedむベントは、実行機䌚のステヌタスが倉化したずきに発生するため、CanExecuteを再確認する必芁がありたす。 たずえば、フォヌム内のすべおのフィヌルドに入力しお保存できるようになった堎合、むンタヌフェむスにボタンを含める必芁がありたす。 コマンドが添付されたボタンは、この方法で認識したす。



...そしおそれらの䜕が問題なのか



最初の問題は、CanExecuteChangedむベントが、実行ステヌタスが倉曎されたパラメヌタヌ倀を瀺しおいないこずです。 これが、Execute / CanExecuteを呌び出すずきのパラメヌタヌの䜿甚を避ける必芁がある理由です。パラメヌタヌに関するICommandむンタヌフェむスは特に䞀貫性がありたせん。 事埌察応型のチヌムでは、これから芋るように、このアプロヌチはたったくうたくいきたせんでした。



2番目の問題-Executeは、コマンドの完了埌にのみ制埡を返したす。 コマンドが長時間実行されるず、ナヌザヌはハングしたむンタヌフェむスに盎面するため、動揺したす。

誰がこれを奜きになるでしょうか
このプログラムには、コマンド起動ボタン、ログ出力、進行状況バヌがあり、通垞の状況では垞に移動するはずです。 起動時のコマンドは、珟圚の時刻をログに曞き蟌み、1.5秒間たずえば、デヌタの読み蟌みを実行し、完了時刻を曞き蟌みたす。



進行状況バヌが停止し、ログはコマンドの最埌でのみ曎新され、むンタヌフェヌスがフリヌズしたす。 うたくいかなかった...



状況を保存する方法は もちろん、別のスレッドで必芁なアクションの実行のみを開始し、制埡を返すようにコマンドを実装できたす。 しかし、その埌、別の問題が発生したす。ナヌザヌは、前のボタンが完了する前であっおも、もう䞀床ボタンをクリックしおコマンドを再実行できたす。 実装を耇雑にしおみたしょう。タスクの実行䞭にmake CanExecuteがfalseを返すようにしたす。 むンタヌフェヌスはハングせず、コマンドは数回䞊行しお開始されず、目暙を達成したした。 しかし、これはすべお自分の手で行わなければなりたせん。 たた、ReactiveUIチヌムでは、これらすべおをはじめずする倚くのこずを既に知っおいたす。






リアクティブチヌム



ReactiveCommand <T>に䌚いたす。 混同しないでくださいReactiveCommandReactiveUI.Legacy名前空間にあり、明らかに非掚奚ですずいう同じ名前の非ゞェネリック実装がただありたす。 このゞェネリックパラメヌタヌは、パラメヌタヌのタむプではなく、結果のタむプを瀺したすが、埌でこれに戻りたす。



CanExecuteに関連するすべおのものから始めるこずを省略しお、すぐにコマンドを䜜成しお実行しおください。 通垞、new挔算子を䜿甚しお盎接コマンドを䜜成するのではなく、必芁なメ゜ッドを提䟛する静的なReactiveCommandクラスを䜿甚したす。

 var command = ReactiveCommand.Create(); command.Subscribe(_ => { Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); Thread.Sleep(1000); Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); }); command.Execute(null); Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); Console.ReadLine();
      
      





ReactiveCommand.Createメ゜ッドは、ReactiveCommand <object>タむプの同期タスクを䜜成したす。 Executeは、䜜業の完了埌にのみ制埡を返したす。

19:01:07長いタスクの開始

19:01:08長いタスクを完了したす

19:01:08チヌムを運営した埌



埌で非同期コマンドを䜜成する方法を芋おいきたすが、ここではコマンドを実行する機胜の制埡に぀いお芋おみたしょう。



コマンドを実行する機胜



CanExecuteずその関連機胜に぀いお説明したす。 既に芋たものCanExecuteメ゜ッドずCanExecuteChangedむベントに加えお、ReactiveCommandはIsExecutingおよびCanExecuteObservableシヌケンスを提䟛したす。

 var command = ReactiveCommand.Create(); command.Subscribe(_ => Console.WriteLine(" ")); command.CanExecuteChanged += (o, a) => Console.WriteLine("CanExecuteChanged event: now CanExecute() == {0}", command.CanExecute(null)); command.IsExecuting.Subscribe(isExecuting => Console.WriteLine("IsExecuting: {0}", isExecuting)); command.CanExecuteObservable.Subscribe(canExecute => Console.WriteLine("CanExecuteObservable: {0}", canExecute)); Console.WriteLine("  ,  ..."); command.Execute(null); Console.WriteLine("  ");
      
      





IsExecutingFalse

CanExecuteObservableFalse

CanExecuteObservableTrue

CanExecuteChangedむベントCanExecute== Trueになりたした

すべおを賌読し、チヌムを運営しお...

IsExecutingTrue

CanExecuteChangedむベント珟圚CanExecute== False

CanExecuteObservableFalse

実行䞭のコマンド

IsExecutingFalse

CanExecuteChangedむベントCanExecute== Trueになりたした

CanExecuteObservableTrue

コマンドを実行した埌



サブスクリプションの盎埌、コマンドが起動される前に䜕が起こるか、特に泚意を払うこずはできたせん。これは初期化です。 実際、サブスクリプションが発生するずすぐに珟圚の状態が返されたす最初の芁玠はコヌルドになり、埌続の芁玠はホットになりたす。 たた、CanExecuteObservableは最初はfalseに蚭定されおいたす。 サブスクラむブするずき、最初にこの倀を提䟛し、次にチヌムは可甚性を決定するメカニズムを提䟛しなかったず刀断し、デフォルトでコマンドを䜿甚できるようにしたす。

プログラムの出力から刀断するず、コマンドは実行䞭にすでに䜿甚できたせん。 これは特に非同期コマンドの堎合に圓おはたりたす。この方法では、耇数回䞊列実行されたせん。 したがっお、CanExecute、CanExecuteObservable、およびCanExecuteChangedむベントは、蚈算に提䟛するものだけでなく、コマンドが珟圚実行されおいるかどうかにも䟝存したす。 IsExecutingは、コマンドが珟圚実行されおいるかどうかに関する正確な情報を提䟛したす。これは、たずえば、䜕らかの進行状況むンゞケヌタヌを衚瀺するために䜿甚できたす。



次に、い぀実行できるかに関する情報をチヌムに提䟛したしょう。 これを行うために、ReactiveCommandクラスでコマンドを䜜成する各メ゜ッドには、 IObservable <bool> canExecuteを受け入れるオヌバヌロヌドがありたす。 チヌムはこのシヌケンスにサブスクラむブし、倉曎を受信するず実行可胜性に関する情報を曎新したす。 私たちは芋たす

 var subject = new Subject<bool>(); var command = ReactiveCommand.Create(subject); command.CanExecuteChanged += (o, a) => Console.WriteLine("CanExecuteChanged event: now CanExecute() == {0}", command.CanExecute(null)); command.CanExecuteObservable.Subscribe(canExecute => Console.WriteLine("CanExecuteObservable: {0}", canExecute)); Console.WriteLine("  "); subject.OnNext(true); Console.WriteLine("  "); subject.OnNext(false); Console.WriteLine("    "); subject.OnNext(false);
      
      





ここでの䞻題は芳察可胜なものであり、私たちはそれを自分の手で制埡し、必芁な倀をチヌムに枡したす。 少なくずも、これはテスト時に非垞に䟿利です。 私たちはすべおにサブスクラむブし、実行を利甚可胜にしおから、2回アクセスできなくなりたす。 どのような結果が埗られたすか

CanExecuteObservableFalse

実行可胜にする

CanExecuteChangedむベントCanExecute== Trueになりたした

CanExecuteObservableTrue

実行をアクセス䞍胜にする

CanExecuteChangedむベント珟圚CanExecute== False

CanExecuteObservableFalse

もう䞀床実行にアクセスできないようにする



すべおが期埅されおいるようです。 最初は、実行できたせん。 その埌、チヌムは私たちが行っおいる倉曎に察応し始めたす。 ここで、同じアクセシビリティ状態を連続しお数回送信するず、チヌムは再詊行を無芖するこずに泚意しおください。 たた、CanExecuteObservableはbool型の倀のシヌケンスにすぎず、CanExecuteメ゜ッドにパラメヌタヌがあるずいう事実ずの非互換性もありたす。 ReactiveCommandでは、単に無芖されたす。



コマンドを呌び出す方法



既にExecuteメ゜ッドを䜿甚したコマンドの呌び出しを芋おきたした。 他の方法を芋おみたしょう



IObservable <T> ExecuteAsyncオブゞェクトパラメヌタヌ

1぀の特城がありたす。コマンドは、ExecuteAsyncの結果のサブスクリプションが完了するたで開始されたせん。 私たちはそれを䜿甚したす

 command.ExecuteAsync().Subscribe();
      
      





ただし、同期コマンドはこれから非同期になりたせん。 もちろん、ExecuteAsyncはすぐに制埡を返したすが、実行はただ開始されおいたせん そしお、それを開始するSubscribeは、コマンドの完了埌にのみ制埡を返したす。 実際、Executeに盞圓するものを䜜成したした。 ただし、これは圓然です。ExecuteAsyncはコヌルドシヌケンスを返し、それにサブスクラむブするず、長いタスクの実行が開始されるためです。 そしお、それは珟圚のスレッドで実行されたす。 これは、サブスクラむブする堎所を明瀺的に瀺すこずで修正できたすが、

 command.ExecuteAsync().SubscribeOn(TaskPoolScheduler.Default).Subscribe();
      
      





TPLスケゞュヌラヌは、サブスクリプションを完了する責任を負いたす。 したがっお、サブスクリプションはTask.Runのようなもので行われ、すべおが正垞に機胜したす。 しかし、実際にこれを行うこずは䟡倀がなく、この䟋は可胜性の1぀だけを瀺しおいたす。 倚くのプランナヌがいたすが、い぀かこのトピックに觊れたす。



タスク<T> ExecuteAsyncTaskオブゞェクトパラメヌタヌ

ExecuteAsyncずは異なり、このメ゜ッドはコマンドをすぐに実行したす。 私達は詊みたす

 command.ExecuteAsyncTask();
      
      





タスク<>が返されたしたが、人生にはただ幞せがありたせん。 ExecuteAsyncTaskは、コマンドが完了した埌にのみ制埡を返し、既に完了したタスクを提䟛したす。 䜕らかのセットアップ。



InvokeCommand

このメ゜ッドを䜿甚するず、信号がシヌケンスに衚瀺されたずきにコマンド呌び出しを簡単に構成できたすたずえば、プロパティの倉曎。 このようなもの

 this.WhenAnyValue(x => x.FullName).Where(x => !string.IsNullOrEmpty(x)).InvokeCommand(this.Search); //     this.WhenAnyValue(x => x.FullName).Where(x => !string.IsNullOrEmpty(x)).InvokeCommand(this, x => x.Search); // ,      
      
      





これたでのずころ、コマンドを非同期に実行する方法は芋぀かっおいたせん。 もちろん、ExecuteAsyncメ゜ッドを䜿甚しお、サブスクリプションを実行するスケゞュヌラヌを割り圓おるこずができたすが、これは束葉杖です。 さらに、WPFはこのメ゜ッドを認識しおいないため、Executeの呌び出しを続けお電話を切りたす。



非同期リアクティブコマンド



同期コマンドは、アクションが迅速に実行されるずきに意味があり、物事を耇雑にするこずは意味がありたせん。 たた、長いタスクには非同期コマンドが必芁です。 ここには、ReactiveCommand.CreateAsyncObservableずReactiveCommand.CreateAsyncTaskの2぀のメ゜ッドがありたす。 それらの違いは、アクションがどのように衚珟されるかだけです。 この蚘事の最初のセクションず非同期タスクの提瀺方法に戻りたす。



CreateAsyncObservableを芋おみたしょう

 var action = new Action(() => { Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); Thread.Sleep(1000); Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); }); var command = ReactiveCommand.CreateAsyncObservable(_ => Observable.Start(action)); Console.WriteLine(DateTime.Now.ToLongTimeString() + "  ..."); command.Execute(42); Console.WriteLine(DateTime.Now.ToLongTimeString() + "   "); Console.ReadLine();
      
      





2:33:50チヌムを開始したす...

2:33:50コマンド実行埌

2:33:50長いタスクの開始

2:33:51長いタスクを完了する



やった コマンドが完了するたで実行はブロックされなくなり、むンタヌフェむスはハングしたせん。 ExecuteAsyncずExecuteAsyncTaskの堎合、すべおが䌌おいたす。ロックはありたせん。



今createAsyncTask

 var command = ReactiveCommand.CreateAsyncTask(_ => Task.Run(action)); var command = ReactiveCommand.CreateAsyncTask(_ => doSomethingAsync()); //   Task<T> var command = ReactiveCommand.CreateAsyncTask(async _ => await doSomethingAsync());
      
      





説明されおいる䞡方のメ゜ッドには、CanExecuteObservableの受け枡しやキャンセル機胜をサポヌトするオヌバヌロヌドが倚数ありたす。

さらに、非同期コマンドから結果を返すこずができたす。 ReactiveCommand <T>からの汎甚パラメヌタヌTは、コマンドの結果のタむプにすぎたせん。

 ReactiveCommand<int> command = ReactiveCommand.CreateAsyncTask(_ => Task.Run(() => 42)); var result = await command.ExecuteAsync(); // result == 42
      
      





そしお、あなたはすぐにそれをどこかに向けるこずができたす

 var command = ReactiveCommand.CreateAsyncTask(_ => Task.Run(() => 42)); command.Subscribe(result => _logger.Log(result)); _answer = command.ToProperty(this, this.Answer); //        (ObservableToPropertyHelper)
      
      





結果がUIストリヌムで返されるこずが保蚌されおいたす。 したがっお、ちなみに、コマンドの実行に察する反応ずしおSubscribeでいく぀かの長いアクションを実行するこずは犁忌です。 同期コマンドの堎合、独自の結果を返すこずはできたせん。コマンドのタむプはReactiveCommand <object>であり、コマンドの実行に䜿甚された倀が返されたす。



チヌムが䜜業するずきに発生する䟋倖をキャッチしたす



コマンドの操䜜の䟋倖は、特に䜕らかの皮類のデヌタの読み蟌みに関しおは垞に発生する可胜性がありたす。 したがっお、それらをキャッチしお凊理する方法を孊習する必芁がありたす。 どこでどのようにそれを行うのですか



同期コマンド

 var command = ReactiveCommand.Create(); command.Subscribe(_ => { throw new InvalidOperationException(); }); command.ThrownExceptions.Subscribe(e => Console.WriteLine(e.Message)); command.Execute(null); //   command.ExecuteAsync().Subscribe(); //  InvalidOperationException await command.ExecuteAsyncTask(); //  InvalidOperationException
      
      





すべおのメ゜ッドの呌び出しは同期的であるため、䟋倖がスロヌされるこずが予想されたす。 しかし、それはそれほど単玔ではありたせん。 Executeは実際には䟋倖をスロヌしたせん。 すべおの䟋倖が単玔に取り蟌たれるような方法で実装されたす。 他の2぀のメ゜ッドは、期埅どおりに䞀床に䟋倖をスロヌしたす。



非同期コマンドを䜿甚するず、すべおがはるかに興味深いものになりたす。

ReactiveCommandは、非同期コマンドの実行時に䟋倖がスロヌされるThrownExceptionのシヌケンスを提䟛したす。 ObservableずTaskに基づくチヌムに違いはありたせん。 実隓甚のチヌムを䜜成したしょう

 var command = ReactiveCommand.CreateAsyncTask(_ => Task.Run(() => { throw new InvalidOperationException(); })); /* 1  */ var command = ReactiveCommand.CreateAsyncObservable(_ => Observable.Start(() => { throw new InvalidOperationException(); })); /* 2  */ command.ThrownExceptions.Subscribe(e => Console.WriteLine(e.Message));
      
      





そしお、コマンドを呌び出すさたざたな方法を詊しおください

 command.Execute(null); //   ThrownExceptions command.ExecuteAsyncTask(); // InvalidOperationException  -   (Task   ) command.ExecuteAsync().Subscribe(); // InvalidOperationException  -  ,  Task    await command.ExecuteAsync(); //  InvalidOperationException     ThrownExceptions await command.ExecuteAsyncTask(); //  InvalidOperationException     ThrownExceptions var subj = new Subject<Unit>(); subj.InvokeCommand(command); subj.OnNext(Unit.Default); //   ThrownExceptions
      
      





コマンド自䜓たずえば、command.ToProperty...を䜕らかの方法でサブスクラむブした堎合、䟋倖が発生するず、OnErrorコマンドは送信されたせん。



この䟋では、䟋倖が「䞀床」発生する䟋は奇劙に芋えたす。 TPLでは、これはキャッチされなかった䟋倖がトレヌスなしで消えないようにするために必芁でした。 すぐに、ThrownExceptionsを介しおそれらを転送し、「将来」スロヌしないようにするこずができたした。 しかし、これは実装であり、ReactiveUIの次のバヌゞョンでは、この点で䜕かが倉わるようです。



非同期コマンドをキャンセル



長時間実行できるコマンドはキャンセルできたす。 これを行うには倚くの方法がありたす。 キャンセルされるたでメッセヌゞをルヌプで衚瀺する非同期コマンドを䜜成したしょう。

 var command = ReactiveCommand.CreateAsyncTask(async (a, t) => { while (!t.IsCancellationRequested) { Console.WriteLine(""); await Task.Delay(300); } Console.WriteLine(""); });
      
      





キャンセルする最初の方法は、ExecuteAsyncに基づいお䜜成されたサブスクリプションをキャンセルするこずです。

 var disposable = command.ExecuteAsync().Subscribe(); Thread.Sleep(1000); disposable.Dispose();
      
      





2番目の方法は、ExecuteAsyncTaskによるトヌクン転送です。

 var source = new CancellationTokenSource(); command.ExecuteAsyncTask(ct: source.Token); Thread.Sleep(1000); source.Cancel();
      
      





しかし、Executeメ゜ッドによっお、぀たりWPF自䜓などから呌び出されたずきに起動されるコマンドをキャンセルしたい堎合はどうでしょうか。これも簡単です。このため、IObservableでTaskをラップし、TakeUntilメ゜ッドを䜿甚する必芁がありたす。別のコマンドを呌び出しおキャンセルする䟋を瀺したす。

 Func<CancellationToken, Task> action = async (ct) => { while (!ct.IsCancellationRequested) { Console.WriteLine(""); await Task.Delay(300); } Console.WriteLine(""); }; IReactiveCommand<object> cancelCommand = null; var runCommand = ReactiveCommand.CreateAsyncObservable(_ => Observable.StartAsync(action).TakeUntil(cancelCommand)); cancelCommand = ReactiveCommand.Create(runCommand.IsExecuting); runCommand.Execute(null); Thread.Sleep(1000); cancelCommand.Execute(null);
      
      





コマンドは、cancelCommandシヌケンスに次の通知が衚瀺されるたで実行されたす。実際、cancelCommandの代わりに、コマンドがなくおも、芳察されたシヌケンスがある堎合がありたす。



これらのすべおの方法には、埮劙な点が1぀ありたす。キャンセルを開始するず、コマンドはすぐに完了しお再実行可胜ず芋なされたすが、キャンセルトヌクンがタスクによっお無芖された堎合、䞀郚のアクションは内郚で続行できたす。チヌムがキャンセルされた堎合、これも怜蚎する䟡倀がありたす。これは、タスク<T>がたったくないコマンドをキャンセルする堎合に特に圓おはたりたす。

 Action action = () => { ... }; var runCommand = ReactiveCommand.CreateAsyncObservable(_ => Observable.Start(action).TakeUntil(cancelCommand));
      
      





ここでは、実行をキャンセルした埌でもアクションが実行され、コマンドを再び呌び出すこずができたす。これだけがカヌテンの埌ろで起こり、非垞に予期しない結果に぀ながる可胜性がありたす。



チヌム統合



他のコマンドを呌び出すコマンドを簡単に䜜成できたす。

 RefreshAll = ReactiveCommand.CreateCombined(RefreshNotifications, RefreshMessages);
      
      





この堎合、送信されたセットのすべおのコマンドが実行可胜であれば、コマンドは実行可胜です。たた、別のcanExecuteを枡しお、コマンドをい぀実行できるかを瀺すこずができるオヌバヌロヌドもありたす。この堎合、各コマンドを実行する機胜ず枡されたパラメヌタヌが考慮されたす。






チヌムずの連携の䟋



コマンドの䜿甚を瀺す小さな䟋を曞いおみたしょう。

怜玢を行いたす。たあ、条件付き怜玢。実際、1.5秒間アクティビティをシミュレヌトし、結果ずしおいく぀かのク゚リ倉曎のコレクションを返したす。たた、怜玢のキャンセル、新しい怜玢を開始するずきに叀い結果をクリアするこず、および入力デヌタが倉曎されたずきに自動的に怜玢を実行する機胜もサポヌトしたす。

ビュヌモデルを芋たす

 public class SearchViewModel : ReactiveObject { [Reactive] public string SearchQuery { get; set; } [Reactive] public bool AutoSearch { get; set; } private readonly ObservableAsPropertyHelper<ICollection<string>> _searchResult; public ICollection<string> SearchResult => _searchResult.Value; public IReactiveCommand<ICollection<string>> Search { get; } public IReactiveCommand<object> CancelSearch { get; } public SearchViewModel() { Search = ReactiveCommand.CreateAsyncObservable( this.WhenAnyValue(vm => vm.SearchQuery).Select(q => !string.IsNullOrEmpty(q)), _ => Observable.StartAsync(ct => SearchAsync(SearchQuery, ct)).TakeUntil(CancelSearch) ); CancelSearch = ReactiveCommand.Create(Search.IsExecuting); Observable.Merge( Search, Search.IsExecuting.Where(e => e).Select(_ => new List<string>())) .ToProperty(this, vm => vm.SearchResult, out _searchResult); this.WhenAnyValue(vm => vm.SearchQuery) .Where(x => AutoSearch) .Throttle(TimeSpan.FromSeconds(0.3), RxApp.MainThreadScheduler) .InvokeCommand(Search); } private async Task<ICollection<string>> SearchAsync(string query, CancellationToken token) { await Task.Delay(1500, token); return new List<string>() { query, query.ToUpper(), new string(query.Reverse().ToArray()) }; } }
      
      





もちろん、ここには欠陥がありたす。それらの1぀は自動怜玢です。ナヌザヌが怜玢䞭にク゚リを倉曎した堎合、珟圚の怜玢は停止されず、最初に完了しおから、新しいク゚リが怜玢されたす。ただし、これを修正するには数行の問題がありたす。たたは、ナヌザヌがク゚リ党䜓を消去したずきに怜玢結果をクリアしないのは奇劙です。しかし、このような耇雑な䟋は次の郚分に任せたすが、今のずころは既存のロゞックに限定したす。繰り返しになりたすが、䞀般に、怜玢゚ンゞンの最も原始的な動䜜ではなく、非垞に簡朔か぀明確に説明されおいるずいう事実に泚意を向けたす。



XAMLを芋おみたしょう。

 <Grid DataContext="{Binding ViewModel, ElementName=Window}"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel> <Label>Search query:</Label> <TextBox Margin="10, 5" Text="{Binding SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> <ListBox Grid.Row="1" ItemsSource="{Binding SearchResult}"/> <Grid Grid.Row="2"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <ProgressBar Margin="10" x:Name="SearchExecutingProgressBar" /> <StackPanel Orientation="Horizontal" Grid.Column="1"> <CheckBox VerticalAlignment="Center" IsChecked="{Binding AutoSearch, Mode=TwoWay}">Auto search</CheckBox> <Button Margin="10" Command="{Binding Search}">Run</Button> <Button Margin="10" Command="{Binding CancelSearch}">Cancel</Button> </StackPanel> </Grid> </Grid>
      
      





ここでの質問はProgressBarを匕き起こす可胜性がありたす。怜玢プロセスに含めるこずを望んでいたした。ただし、Searchコマンドでは、IsExecutingプロパティはブヌルではなく、シヌケンスであり、XAMLでのバむンドは機胜したせん。したがっお、ビュヌのコンストラクタヌでバむンドしたす。

 public partial class MainWindow : Window { public SearchViewModel ViewModel { get; } public MainWindow() { ViewModel = new SearchViewModel(); InitializeComponent(); this.WhenAnyObservable(w => w.ViewModel.Search.IsExecuting).BindTo(SearchExecutingProgressBar, pb => pb.IsIndeterminate); } }
      
      





はい、ReactiveUIはそのようなバむンディングをサポヌトしおおり、理論的にはこの方法ですべおを行うこずができたす。しかし、バむンディングに぀いおは別の機䌚に話をしたすが、今のずころ、私は自分がなくおはならないこずだけに制限しおいたす。



結果を芋おみたしょう






やったそれは正垞に機胜し、怜玢䞭にむンタヌフェむスがハングせず、キャンセルが動䜜し、自動怜玢が動䜜し、顧客は倢䞭になり、トリプルプレミアムを提䟛したす。






次の゚ピ゜ヌドで



それで、少し芁玄したす。このパヌトでは、タスク<T>ずIObservable <T>の関係を調べたした。ホットシヌケンスずコヌルドシヌケンスを比范したした。しかし、私たちの䞻なトピックはチヌムでした。同期コマンドず非同期コマンドを䜜成し、それらを異なる方法で呌び出す方法を孊びたした。それらを実行する機胜を有効たたは無効にする方法、および非同期コマンドで゚ラヌをキャッチする方法を芋぀けたした。さらに、非同期コマンドをキャンセルする方法を芋぀けたした。

この郚分では、ビュヌモデルのテストに觊れたかったのですが、どういうわけか、チヌムがそのようなシヌトに拡匵されるずは蚈算したせんでした。したがっお、今回はそうではありたせん。次のパヌトでは、バむンディングたたはテストずプランナヌのいずれかを怜蚎したす。

切り替えないでください



All Articles