2

モーダルダイアログをシミュレートするようにコードを更新しようとしていますが、Reactive Extensionsはそれを行うのに適切なツールのように感じましたが、機能させることができません。

現在、コードは次のようになっています。

public bool ShowConfirmationDialogs(IEnumerable<ItemType> items)
{
    bool canContinue = true;

    foreach (var item in items)
    {
        Dialog dialog = new Dialog();
        dialog.Prepare(item);   // Prepares a "dialog" specific to each item

        IObservable<bool> o = Service.ShowDialog(dialog, result =>
        {
             // do stuff with result that may impact next iteration, e.g.
             canContinue = !result.Condition;
        });

        // tried using the following line to wait for observable to complete
        // but it blocks the UI thread
        o.FirstOrDefault();

        if (!canContinue)
            break;
    }

    if (!canContinue)
    {
        // do something that changes current object's state
    }

    return canContinue;
}

これまで、ラムダ式のコードは、で示される「ダイアログ」ShowDialogが閉じられたときに処理を行うために使用されていました。の呼び出しShowDialogは非ブロッキングであり、を返すために使用されvoidます。

舞台裏で行われるShowDialogのは、オブジェクトがObservableCollection画面に表示されるようにに追加されることです。

ダイアログが閉じられたときにサブスクライバーのwho呼び出しをShowDialog返すように変更しました。これは機能します。次のコードでテストしました。IObservable<bool>OnCompleted

o.Subscribe(b => Console.WriteLine(b), () => Console.WriteLine("Completed"));

そして"Completed"、ダイアログを閉じると文字列が表示されます。私の問題は、上の行が非ブロッキングであるため、いくつかのダイアログを表示できる可能性があることですが、これはやりたくないことです。

私は次のことを試しました:

o.FirstOrDefault();

プログラムは、オブザーバブルが何かを送信するか完了するまでそこで待機すると想定します。プログラムは大丈夫ですが、UIもフリーズします。つまり、ダイアログが表示されないため、閉じることができないため、observableが完了しません。

ObserveOnを使用していくつかのバリエーションをSubscribeOn試し、UIスレッドにその作業を任せようとしましたが、運がありませんでした。どんなアイデアでも大歓迎です。私の主な目標は、を使用するときのように、コードをシーケンシャルに見せることWindow.ShowDialogです。

要約すると:(そしてコメントでクリスに答える)

問題は、ShowDialog非ブロッキングであり、前述のように、期待される動作はを使用した場合と同じであるということWindow.ShowDialogです。現在、ブロックすることはできませんが、ループが続行され、いくつかのダイアログが表示されます。または、(を使用してFirstOrDefault)ブロックすることもできますが、UIもブロックされるため、オブザーバブルを完了するためにダイアログを閉じることができません。

より多くの説明:(謎めきのために)

呼び出すとShowDialog、モーダルのコントロールが表示されます(ユーザーがアプリケーションの残りの部分にアクセスするのをブロックするという意味で)が、メソッドの呼び出しはブロックされないため、実行はすぐに続行されます。私の例では、ループが原因で複数のダイアログが表示される可能性があります。コレクションにオブジェクトを追加するだけで、この動作を変更できないため、このメソッドは非ブロッキングです。

ただし、Rxを使用したいので、ShowDialogが返されるようにしましたIObservable。そのため、メソッドはすぐに戻りますが、のアクションによって表示されたコントロールが閉じられるOnCompletedと、オブザーバーを呼び出すオブジェクトがあります。それが重要な場合に備えて、ShowDialog私はこれにを使用しています。Subject

私が今欲しいのは、これIObservableが完了するのを待ってから先に進むことです。したがって、ブロッキング呼び出しをシミュレートします。FirstOrDefault待機部分は正常に実行されますが、残念ながら、UIスレッドもブロックされ、コントロールが実際に表示されなくなり、ユーザーがコントロールを閉じることができなくなり、IObservable完了できなくなります。

x秒後にダイアログを自動的に閉じることで、ある種の作業を行うことができるので、私の考えは遠くないことを知っています。今必要なのは、ユーザーがタイマーの代わりにコントロールを閉じることができるように、UIをブロックしない「待機」部分です。

4

2 に答える 2

3

問題の解決策を見つけたので、興味があれば共有します。

いくつかのリファクタリングの後、質問で使用したサービスのメソッドの名前を変更し、新しいメソッドを作成しました。インターフェイスは次のようになります。

public interface IDialogService
{
    /// <summary>
    /// Displays the specified dialog.
    /// </summary>
    /// <remarks>This method is non-blocking. If you need to access the return value of the dialog, you can either
    /// provide a callback method or subscribe to the <see cref="T:System.IObservable{bool?}" /> that is returned.</remarks>
    /// <param name="dialog">The dialog to display.</param>
    /// <param name="callback">The callback to be called when the dialog closes.</param>
    /// <returns>An <see cref="T:System.IObservable{bool?}" /> that broadcasts the value returned by the dialog
    /// to any observers.</returns>
    IObservable<bool?> Show(Dialog dialog, Action<bool?> callback = null);

    /// <summary>
    /// Displays the specified dialog. This method waits for the dialog to close before it returns.
    /// </summary>
    /// <remarks>This method waits for the dialog to close before it returns. If you need to show a dialog and
    /// return immediately, use <see cref="M:Show"/>.</remarks>
    /// <param name="dialog">The dialog to display.</param>
    /// <returns>The value returned by the dialog.</returns>
    bool? ShowDialog(Dialog dialog);
}

私の問題を解決する部分は、次の実装ですShowDialog

public bool? ShowDialog(Dialog dialog)
{
    // This will hold the result returned by the dialog
    bool? result = null;

    // We show a dialog using the method that returns an IObservable
    var subject = this.Show(dialog);

    // but we have to wait for it to close on another thread, otherwise we'll block the UI
    // we do this by preparing  a new DispatcherFrame that exits when we get a value
    // back from the dialog
    DispatcherFrame frame = new DispatcherFrame();

    // So start observing on a new thread. The Start method will return immediately.
    new Thread((ThreadStart)(() =>
    {
        // This line will block on the new thread until the subject sends an OnNext or an OnComplete
        result = subject.FirstOrDefault();

        // once we get the result from the dialog, we can tell the frame to stop
        frame.Continue = false;
    })).Start();

    // This gets executed immediately after Thread.Start
    // The Dispatcher will now wait for the frame to stop before continuing
    // but since we are not blocking the current frame, the UI is still responsive
    Dispatcher.PushFrame(frame);

    return result;
}

コメントはコードを理解するのに十分なはずだと思います。コードは次のように使用できます。

public bool? ShowConfirmationDialogs(IEnumerable<ItemType> items)
{
    bool canContinue = true;

    foreach (var item in items)
    {
        Dialog dialog = new Dialog();
        dialog.Prepare(item);   // Prepares a "dialog" specific to each item

        bool? result = Service.ShowDialog(dialog);

        canContinue = result.HasValue && result.Value;

        if (!canContinue)
            break;
    }

    if (!canContinue)
    {
        // do something that changes current object's state
    }

    return canContinue;
}

他のユーザーからのコメントや代替案をお聞かせください。

于 2012-06-15T10:29:31.323 に答える
2

あなたのコードは、観察可能なシーケンスと列挙可能なシーケンスの奇妙な組み合わせです。また、オブザーバブルを使用している関数からを返そうとしているboolため、ブロッキング操作を要求するという悪い習慣を強いられています。

すべてを観察可能なものとして維持するように努めるのが最善です。これはベストプラクティスのアプローチです。

itemまた、関数内でそれぞれを明確に使用することはできませんShowDialog

これは、私が今のところ、これ以上知らなくても提供できる最高のものです。これを試して:

public IObservable<bool> ShowConfirmationDialogs(IEnumerable items)
{
    var query =
        from item in items.OfType<SOMEBASETYPE>().ToObservable()
        from result in Service.ShowDialog(item =>
        {
            // do stuff with result that may impact next iteration of foreach
        })
        select new
        {
            Item = item,
            Result = result,
        };

    return query.TakeWhile(x => x.Result == true);
}

呼び出し元のコードは、UIスレッドを監視する必要があります。

これが役立つかどうか教えてください。

于 2012-06-13T00:26:44.260 に答える