简体   繁体   中英

Using Rx to subscribe to progress event and still get result on completion

I have to call a method belonging to an external component. The signature of the method looks something like this:

IImportedData Import(string fileName, Action<int> progress);

This operation can take a long time to execute, so I need to call it asynchronously and report progress to the user. I'm looking at different approaches to calling it (Rx, TPL, ThreadPool) to find something expressive and clear, however I'm struggling to come up with a way to do it in Rx.

At first glance, the idea of reporting progress using Rx seems like a perfect fit - it's a stream of incoming ints relating to progress. The only catch is that when the operation completes, I need to inspect IImportedData to display a result to the user. OnCompleted isn't intended for that purpose, which leads me down a path of having a class that exposes two IObservable streams, and then a method to "Start" the operation.

private class Importer : IObservable<int>, IObservable<IImportedData>

Feels clunky and I'm sure there's a better way that I don't know of.

Two things come to mind up front:

  1. Task<T> and IProgress<T> seem like a better fit for this task
  2. Implementing IObservable<T> is generally frowned upon. Creating composite instances using static methods on Observable is the recommended approach.

If you stick with Rx, I'd recommend having a look at this discussion I had with the Rx guys a few years back. Jeffrey van Gogh ended up recommending an Either<TLeft, TRight> response that you would automatically route callbacks depending on whether the message was a "progress" event or a "result" event. If I was in that position again, that's certainly the direction I would take.

You could try something like this:

Func<string, Action<int>, IObservable<IImportedData>> create =
    (fileName, progress) =>
        Observable.Create<IImportedData>(o =>
        {
            var foo = new Foo();
            var subject = new BehaviorSubject<int>(0);

            var d1 = subject.Subscribe(progress);

            var result = foo.Import(fileName, n => subject.OnNext(n));

            var d2 = subject
                .Where(x => x == 100)
                .Select(x => result)
                .Subscribe(o);

            return new CompositeDisposable(d1, d2);
        });

I haven't tested it, but something like this should give you a relatively clean Rx way of doing what you're after. You may need to add in your synchonization context.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM