Using Rx Repeat() and Replay() to cache and restart a DNS query

system.reactive

Solution

All right - just realized FromAsyncPatterns are one-offs which essentially can't be restarted, and that they Begin when referenced, not when subscribed.

Replacing

_resolver(host)

with

Observable.Defer(() => _resolver(host))

seems to do the trick.

If no-one objects (and I don't discover other problems), I'll accept this answer shortly.

Problem

I'm a Rx newbie, so I hope you can bear with me. As an exercise for myself, and possibly a sample I can demonstrate for colleagues, I've done two wrapper classes for Dns.BeginGetHostEntry()/EndGetHostEntry(): DnsResolver and DnsResolverRx. The classes each have a single public static method: ``` void Resolve(string host, Action<IPHostEntry> getResult, Control context = null); ``` ...and some additional requirements to make it interesting: 1. if context is provided, getResult must be invoked on the associated thread 2. previous results for the same host are cached for MaxResultAge seconds. The non-Rx version works fine, but is not really relevant to this question. The Rx version looks like this: ``` class DnsResolverRx { static Func<string, IObservable<IPHostEntry>> _resolver = Observable.FromAsyncPattern<string, IPHostEntry>(Dns.BeginGetHostEntry, Dns.EndGetHostEntry); public static void Resolve(string host, Action<IPHostEntry> setResult, Control context = null) { IObservable<IPHostEntry> result; result = _cache.GetOrCreateValue( // a trivial TryGetValue wrapper host, () => _resolver(host) .Do(e => Debug.WriteLine("resolved")) .Repeat() .Do(e => Debug.WriteLine("repeated")) .Replay(MaxResultAge) .RefCount() ); result = result.Take(1); // each request needs only 1 result if (context != null) result = result.ObserveOn(context); result.Subscribe( entry => setResult(entry), ex => setResult(null) ); } } static void Main(string[] args) { for (int i=0; i<10; ++i) { int num = i; Debug.WriteLine("start" + num); DnsResolverRx.Resolve("chief", e => Debug.WriteLine("done"+num)); Thread.Sleep(200); } Console.ReadLine(); } ``` Replay() seems to work, so the first requests within MaxResultAge are all completed, reusing the same result. However, the next request triggers Repeat(), and I end up with a seemingly endless loop: ``` start0 start1 start2 start3 start4 start5 resolved repeated done0 done1 done2 done3 done4 done5 start6 resolved repeated resolved repeated ... and so on ad infinitum ``` Can anyone enlighten me on what's going on, and what I'm doing wrong?

Original source