I am trying to use Rx(.net) for a project and I have an issue with how to properly dispose resources that are created during Observable.Create()
and emitted with OnNext()
. My setup looks like this (shortened to the relevant bits only, hopefully):
var obs = Observable.Create<ReactiveRunData>(async (o) =>
{
if (someCondition)
{
RunData runData = await CreateRunData(); // RunData is IDisposable, needs to be disposed
o.OnNext(runData);
}
o.OnCompleted();
return Disposable.Empty;
})
.Concat(Observable.Empty<RunData>().Delay(TimeSpan.FromSeconds(2)))
.Repeat() // Resubscribe indefinitely after source completes
.Publish().RefCount() // see http://northhorizon.net/2011/sharing-in-rx/
;
This is my implementation of an observable collection that's infinite and produces an item (of type RunData
) every 2 seconds.
Then I do the actual reactive stuff by transforming the IObservable stream using all kinds of operators:
var final = obs
.Select(runData => ...)
// lots of other operations
.Select(tuple => (tuple.runData, tuple.result));`
The final observable returns a tuple (RunData, Result)
.
When subscribing to that observable, at the end I explicitly dispose of the RunData
instances, like so:
final.Subscribe(
async (tuple) =>
{
var (runData, result) = tuple;
try
{
// ... do something with result and runData, await some stuff, ...
} catch (Exception e)
{
// error handling
} finally
{
// dispose of runData
runData.Dispose();
}
},
(Exception e) =>
{
// error handling
});
I suspect this implementation is leaky in various ways, such as when exceptions are thrown from different places, in some cases of which I believe the RunData instance won't get disposed, but is just gone, replaced by an exception travelling through the pipe.
I believe I'd also run into problems if I would add a second subscriber to my observable, right? I don't need more than one subscriber, but that also makes me question my implementation.
I feel like the whole idea of passing data that needs to be disposed by subscribers is wrong, but I couldn't come up with a better way. I tried using Observable.Using()
, but afaik that only disposes the resource when the sequence ends, which in my case is never. And I really need an infinite sequence because I want to be able to use functionality like Scan()
to reference previous data to build intermediate data structures over time.
I also tried using the callback that is returned from the lambda of Observable.Create()
but that fires as soon as Observable.Create() is done and not after the subscriber is done, so that led to race conditions (but I'm not even sure if I understood that correctly, RX + async is tricky).
So... how can I implement this properly?
For some background info, RunData
includes (among other things) a DB transaction and an Autofac LifetimeScope
, both of which I want to use throughout the pipeline, and both of which need to be disposed at the end, after the subscriber is done.