そのため、C#4.0の悲しい時代に、IEnumerableの「yieldreturn」継続をハッキングしてオブザーバブルを待機することにより、GUIスレッドで非同期ワークフローを可能にする次の「WorkflowExecutor」クラスを作成しました。したがって、次のコードは、button1Clickで、テキストを更新し、button2をクリックするのを待って、1秒後にループする単純なワークフローを開始するだけです。
public sealed partial class Form1 : Form {
readonly Subject<Unit> _button2Subject = new Subject<Unit>();
readonly WorkflowExecutor _workflowExecutor = new WorkflowExecutor();
public Form1() {
InitializeComponent();
}
IEnumerable<IObservable<Unit>> CreateAsyncHandler() {
Text = "Initializing";
var scheduler = new ControlScheduler(this);
while (true) {
yield return scheduler.WaitTimer(1000);
Text = "Waiting for Click";
yield return _button2Subject;
Text = "Click Detected!";
yield return scheduler.WaitTimer(1000);
Text = "Restarting";
}
}
void button1_Click(object sender, EventArgs e) {
_workflowExecutor.Run(CreateAsyncHandler());
}
void button2_Click(object sender, EventArgs e) {
_button2Subject.OnNext(Unit.Default);
}
void button3_Click(object sender, EventArgs e) {
_workflowExecutor.Stop();
}
}
public static class TimerHelper {
public static IObservable<Unit> WaitTimer(this IScheduler scheduler, double ms) {
return Observable.Timer(TimeSpan.FromMilliseconds(ms), scheduler).Select(_ => Unit.Default);
}
}
public sealed class WorkflowExecutor {
IEnumerator<IObservable<Unit>> _observables;
IDisposable _subscription;
public void Run(IEnumerable<IObservable<Unit>> actions) {
_observables = (actions ?? new IObservable<Unit>[0]).GetEnumerator();
Continue();
}
void Continue() {
if (_subscription != null) {
_subscription.Dispose();
}
if (_observables.MoveNext()) {
_subscription = _observables.Current.Subscribe(_ => Continue());
}
}
public void Stop() {
Run(null);
}
}
非同期作業を行うために「yield」継続を使用するアイデアの賢い部分は、Daniel EarwickerのAsyncIOPipeアイデアから取られました: http://smellegantcode.wordpress.com/2008/12/05/asynchronous-sockets- with-yield-return-of-lambdas / 次に、その上にリアクティブフレームワークを追加しました。
現在、C#5.0の非同期機能を使用してこれを書き直すのに問題がありますが、それは簡単なことのようです。オブザーバブルをタスクに変換すると、それらは1回だけ実行され、whileループは2回目にクラッシュします。それを修正する助けがあれば素晴らしいでしょう。
言われた/尋ねられたすべて、async/awaitメカニズムはWorkflowExecutorが私に与えないものを私に与えますか? WorkflowExecutorで(同じ量のコードが与えられた場合)私がasync/awaitでできることはありますか?
お気づきのように、Taskは、Observableの「イベントのストリーム」とは対照的に、非常に1回限りの使用です。これ(IMHO)の良い考え方は、 Rxチームの2.0ベータ に関する投稿の2x2チャートです。
状況(イベントの1回限りと「ストリーム」)によっては、Observableを維持する方が理にかなっている場合があります。
Reactive 2.0 Betaにホップアップできる場合は、それでオブザーバブルを「待つ」ことができます。たとえば、コードの「async/await」(おおよその)バージョンでの私自身の試みは次のようになります。
public sealed partial class Form1 : Form
{
readonly Subject<Unit> _button2Subject = new Subject<Unit>();
private bool shouldRun = false;
public Form1()
{
InitializeComponent();
}
async Task CreateAsyncHandler()
{
Text = "Initializing";
while (shouldRun)
{
await Task.Delay(1000);
Text = "Waiting for Click";
await _button2Subject.FirstAsync();
Text = "Click Detected!";
await Task.Delay(1000);
Text = "Restarting";
}
}
async void button1_Click(object sender, EventArgs e)
{
shouldRun = true;
await CreateAsyncHandler();
}
void button2_Click(object sender, EventArgs e)
{
_button2Subject.OnNext(Unit.Default);
}
void button3_Click(object sender, EventArgs e)
{
shouldRun = false;
}
}
Jamesが述べたように、Rx v2.0Betaで始まるIObservable <T>シーケンスを待つことができます。動作は、最後の要素(OnCompletedの前)を返すか、観察されたOnErrorをスローすることです。シーケンスに要素が含まれていない場合、InvalidOperationExceptionが発生します。
これを使用していることに注意してください。他のすべての望ましい動作を取得できます。
集計の結果を計算するなど、さらに凝ったことを行うことができますが、DoとScanを使用して中間値を観察します。
var xs = Observable.Range(0, 10, Scheduler.Default);
var res = xs.Scan((x, y) => x + y)
.Do(x => { Console.WriteLine("Busy. Current sum is {0}", x); });
Console.WriteLine("Done! The sum is {0}", await res);