Reactive Extensions:如何观察IEnumerable方法结果异步

时间:2013-10-25 11:17:02

标签: c# wpf system.reactive

我有一个返回我的业务对象的IEnumerable的方法。在此方法中,我将大文本文件的内容解析为业务对象模型。它没有线程化的东西。

在我的ViewModel(WPF)中,我需要存储并显示方法的结果。 Store是一个ObservableCollection。

这是可观察的代码:

private void OpenFile(string file)
{
    _parser = new IhvParser();
    App.Messenger.NotifyColleagues(Actions.ReportContentInfo, new Model.StatusInfoDisplayDTO { Information = "Lade Daten...", Interval = 0 });

    _ihvDataList.Clear();

    var obs = _parser.ParseDataObservable(file)
                     .ToObservable(NewThreadScheduler.Default)
                     .ObserveOnDispatcher()
                     .Subscribe<Ihv>(AddIhvToList, ReportError, ReportComplete);
}

private void ReportComplete()
{
    App.Messenger.NotifyColleagues(Actions.ReportContentInfo, new Model.StatusInfoDisplayDTO { Information = "Daten fertig geladen.", Interval = 3000 });
    RaisePropertyChanged(() => IhvDataList);
}

private void ReportError(Exception ex)
{
    MessageBox.Show("...");
}

private void AddIhvToList(Ihv ihv)
{
    _ihvDataList.Add(ihv);
}

这是解析器代码:

public IEnumerable<Model.Ihv> ParseDataObservable(string file)
{
    using (StreamReader reader = new StreamReader(file))
    {
        var head = reader.ReadLine(); //erste Zeile ist Kopfinformation

        if (!head.Contains("BayBAS") || !head.Contains("2.3.0"))
        {
            _logger.ErrorFormat("Die Datei {0} liegt nicht im BayBAS-Format 2.3.0 vor.");
        }
        else
        {
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
                if (line.Length != 1415)
                {
                    _logger.ErrorFormat("Die Datei {0} liegt nicht im BayBAS-Format 2.3.0 vor.");
                    break;
                }

                var tempIhvItem = Model.Ihv.Parse(line);
                yield return tempIhvItem;
            }
            reader.Close();

        }
    }

}

为什么我没有得到异步的结果?在我的DataGrid中看到结果之前,所有项目都会被解析并传递。

有人可以帮忙吗?

安德烈亚斯

1 个答案:

答案 0 :(得分:3)

你确定这不是异步发生的吗?您是根据您在UI中感知的内容假设这个,还是设置了断点并确定事实上是这样的?

请注意,WPF的Dispatcher使用优先级队列,DispatcherScheduler调度具有Normal优先级的项目,这优先于用于输入,布局和呈现的优先级。如果结果足够快,那么在处理完最后一个结果之后,UI可能不会更新:调度程序可能太忙于处理结果以执行UI的布局和渲染。

您可以尝试覆盖DispatcherScheduler的行为来安排自定义优先级,如下所示:

public class PriorityDispatcherScheduler : DispatcherScheduler
{
    private readonly DispatcherPriority _priority;

    public PriorityDispatcherScheduler(DispatcherPriority priority)
        : this(priority, Dispatcher.CurrentDispatcher) {}

    public PriorityDispatcherScheduler(DispatcherPriority priority, Dispatcher dispatcher)
        : base(dispatcher)
    {
        _priority = priority;
    }

    public override IDisposable Schedule<TState>(TState state, Func<IScheduler, TState, IDisposable> action)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        var d = new SingleAssignmentDisposable();

        this.Dispatcher.BeginInvoke(
            _priority,
            (Action)(() =>
                     {
                         if (d.IsDisposed)
                             return;
                         d.Disposable = action(this, state);
                     }));

        return d;
    }
}

然后将ObserveOnDispatcher()替换为ObserveOn(new PriorityDispatcherScheduler(p))来修改您的可观察序列,其中p是适当的优先级(例如Background)。

,这看起来非常可疑:ToObservable(NewThreadScheduler.Default)。我相信这会导致每次进入结果时都会创建一个新线程,其唯一目的是将其传递给调度程序,之后新线程将终止。这几乎肯定不是你想要的。我假设您只是希望在单独的线程上处理该文件;如上所述,如果您的IEnumerable产生1,000个项目,那么您的代码最终会创建1,000个短期线程,其中没有一个实际上正在执行读取文件的工作。

最后,是否在调度程序线程上调用了OpenFile()?如果是这样,我相信将要发生的事情如下:

  1. Dispatcher(在UI线程上)将调用Subscribe(),它将处理可观察操作符链,一直回到ParseDataObservable(file)
  2. Dispatcher将遍历您的IEnumerable序列,将每个结果触发到ToObservable()创建的可观察序列中。
  3. 传递到可观察序列的每个结果都将安排在调度员(当前正在运行的调度程序)上传递。
  4. 如果是这种情况,则在任何结果传递给AddIhvToList()之前,整个文件将被读取,因为调度程序被绑定读取文件而不会绕过处理队列中的结果,直到它完成。如果发生这种情况,您可以尝试按如下方式更改代码:

    var obs = _parser.ParseDataObservable(file)
                     .ToObservable()
                     .SubscribeOn(/*NewThread*/Scheduler.Default)
                     .ObserveOnDispatcher() // consider using PriorityDispatcherScheduler
                     .Subscribe<Ihv>(AddIhvToList, ReportError, ReportComplete);
    

    注入SubscribeOn()应确保IEnumerable的迭代(即文件的读取)发生在单独的线程上。 Scheduler.Default应该足够了,但如果你真的需要(你可能不需要),你可以使用NewThreadScheduler。调度程序线程将在设置完所有内容后从Subscribe()返回,释放它以继续处理其队列,即将结果传递给AddIhvToList()。这应该为您提供异步行为你渴望。