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关闭时,来自 lambda 表达式的代码被用来做一些事情。调用ShowDialog是非阻塞的,用于返回void

在幕后发生的事情ShowDialog是一个对象被添加到一个ObservableCollection,以便它显示在屏幕上。

我进行了修改ShowDialog,以便在对话框关闭时返回订阅者的IObservable<bool>who 呼叫OnCompleted。这行得通。我使用以下代码对其进行了测试:

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

"Completed"当对话框关闭时,我可以看到字符串。我的问题是上面的行是非阻塞的,所以我可能会显示几个我不想做的对话框。

我尝试了以下方法:

o.FirstOrDefault();

假设程序会在那里等到 observable 发送一些东西或完成。该程序可以阻止,但它也会冻结 UI,这意味着我永远看不到我的对话框,我永远无法关闭,所以 observable 永远不会完成。

我尝试了几种变体ObserveOn,并SubscribeOn尝试让 UI 线程完成其工作,但没有运气。任何想法都将不胜感激,我的主要目标是保持代码看起来连续,有点像使用Window.ShowDialog.

总结一下:(并在评论中回答克里斯)

问题在于它ShowDialog是非阻塞的,如上所述,预期的行为与使用Window.ShowDialog. 现在,我要么不阻塞——但循环继续,我得到几个对话框——或者我可以阻塞(用FirstOrDefault),但它也会阻塞 UI,这会阻止我关闭对话框以完成 observable。

更多解释:(对于 Enigmativity)

当我调用时ShowDialog,显示的控件是模态的——从某种意义上说,它阻止用户访问应用程序的其余部分——但对该方法的调用是非阻塞的,因此执行会立即继续。在我的示例中,由于循环,这可能会显示多个对话框。该方法是非阻塞的,因为它所做的只是将一个对象添加到集合中,而我无法更改此行为。

但是,希望使用 Rx,我做了它,以便ShowDialog返回一个IObservable. 所以现在该方法立即返回,但我有一个对象,一旦关闭OnCompleted由 的操作显示的控件,就会调用任何观察者。ShowDialog我正在Subject为此使用 a ,以防万一。

我现在想要的是在继续之前等待返回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从使用 observables 的函数中返回 a ,因此您强制要求进行阻塞操作的不良做法。

你最好尝试让一切都保持为可观察的。这是最佳实践方法。

函数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 回答