Subscribe to observable and await later - c#

Following would NOT catch the emitted value:
someObservable = Observable.FromEventPattern(this, "MyEvent");
FireMyEvent("foo");
await someObservable.FirstOrDefaultAsync(e => e.Args == "foo");
So I've come across this pattern in my unit tests:
var tsc = new TaskCompletionSource<bool>();
var callback = someObservable.FirstOrDefaultAsync(...);
using (callback.Subscribe(e => tsc.SetResult(e.Args)))
{
FireMyEvent("foo");
tsc.Task.Wait(1000);
}
Assert.AreEqual("foo", tsc.Task.Result);
Basically I need to subscribe, then trigger the action and then await the subscribed observable.
Is it possible to simplify this subscribe/await without introducing new Task, or WaitHandle?

I'm not sure what exactly you're trying to do, but if I understand the basic idea then this works:
var subject = new Subject<Unit>();
var callback = subject.FirstOrDefaultAsync();
using (callback.Subscribe(_ => { }, () => Console.WriteLine("Done.")))
{
subject.OnNext(Unit.Default);
}
Can you explain your use-case more fully?

I don't really understand your assumption, that the await won't catch the result.
You have to await the event before it gets fired
someObservable = Observable.FromEventPattern(this, "MyEvent");
Task.Delay(1000).ContinueWith(_ => FireMyEvent("foo"));
var result = await someObservable.FirstOrDefaultAsync(e => e.Args == "foo");
Your result ist now a EventPattern. However I would recommend a more type safer FromEventPattern-Overload:
var eventObservable = Observable
.FromEventPattern<YourEventArgs>(
h => MyEvent += h,
h => MyEvent -= h);

Related

Make code run in parallel instead of awaiting every single data handler

This is the websocket received message event handler. However these subscriptions are awaited and they are currently not running in parallel, so there is that process time prompt message. The idea is to make these data handlers run in parallel, which means Task.WhenAll should be used. The data handler is of type Func<T, ValueTask>, so it makes sense to be executed in parallel, so that if one of the data handlers contains await Task.Delay(5000), it shouldn't block the others.
private async ValueTask OnDataReceived(DataReceivedEventArgs e)
{
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
foreach (var subscription in _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request)))
{
var userProcessTime = await MeasureUserProcessTime(async () => await subscription.DataHandler(messageEvent));
if (userProcessTime.TotalMilliseconds > 500)
{
_logger.LogTrace("Detected slow data handler ({UserProcessTimeMs} ms user code), consider offloading data handling to another thread. Data from this socket may arrive late or not at all if message processing is continuously slow.",
userProcessTime.TotalMilliseconds);
}
}
}
What about the version below?
private async ValueTask OnDataReceived(DataReceivedEventArgs e)
{
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
var handlers = _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request))
.Select(subscription => subscription.DataHandler(messageEvent).AsTask());
_ = Task.WhenAll(handlers);
}
or
private async ValueTask OnDataReceived(DataReceivedEventArgs e)
{
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
var handlers = _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request))
.Select(subscription => Task.Run(() => subscription.DataHandler(messageEvent)));
await Task.WhenAll(handlers);
}
The version with await Task.WhenAll is fine. I assume you still want to keep MeasureUserProcessTime, though, since that will give you useful warnings for long handlers.
I suppose somewhere you have some synchronization mechanism with SemaphoreSlim entities, but anyway... why don't you simply fire and forget if you don't do anything with the results of your tasks?
private void OnDataReceived(DataReceivedEventArgs e)
{
if (e is null)
{
_logger.Warning("!!!!!!!");
return;
}
var timestamp = DateTimeOffset.Now;
var messageEvent = new MessageEvent(e.Message, timestamp);
foreach (var subscription in _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request)))
{
Task.Run(() =>
{
try
{
var handlers = _subscriptions
.GetAll()
.Where(subscription => MessageMatchesHandler(messageEvent.Data, subscription.Request))
.Select(subscription => subscription.DataHandler(messageEvent).AsTask());
var userProcessTime = await MeasureUserProcessTime(async () => await subscription.DataHandler(messageEvent));
if (userProcessTime.TotalMilliseconds > 500)
{
_logger.LogTrace("Detected slow data handler ({UserProcessTimeMs} ms user code), consider offloading data handling to another thread. Data from this socket may arrive late or not at all if message processing is continuously slow.",
userProcessTime.TotalMilliseconds);
}
}
catch (Exception ex)
{
}
});
}
}

How cancel a debounced Rx event A if a different event B has occured?

I need to cancel a debounced Rx event A if a different event B has occured. A contrived example: ignore a debounced keyboard keystroke if a mouse button was clicked meanwhile.
Below I simulate events A and B via timer delays. A is debounced using Rx.NET Throttle operator:
var subjA = new Subject<int>();
var subjB = new Subject<Unit>();
// desired output: 3 (because B occcurs at 150ms timeline)
// actual output: 2, 3
subjA
.Throttle(TimeSpan.FromMilliseconds(200)).
.Subscribe(s => Console.WriteLine(s));
await Task.WhenAll(EmitA(), EmitB(), Task.Delay(2000));
async Task EmitA()
{
subjA!.OnNext(1);
await Task.Delay(100);
subjA!.OnNext(2);
await Task.Delay(500);
subjA!.OnNext(3);
}
async Task EmitB()
{
await Task.Delay(150);
subjB!.OnNext(Unit.Default);
}
I can solve this by giving up Throttle and using Select/Delay/TakeUntil/Switch, try the fiddle:
#nullable enable
using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using System;
var subjA = new Subject<int>();
var subjB = new Subject<Unit>();
subjA.Select(n =>
Observable.Return(n).Delay(TimeSpan.FromMilliseconds(200))
.TakeUntil(subjB))
.Switch()
.Subscribe(s => Console.WriteLine(s));
await Task.WhenAll(EmitA(), EmitB(), Task.Delay(2000));
async Task EmitA()
{
subjA!.OnNext(1);
await Task.Delay(100);
subjA!.OnNext(2);
await Task.Delay(500);
subjA!.OnNext(3);
}
async Task EmitB()
{
await Task.Delay(150);
subjB!.OnNext(Unit.Default);
}
Though, this feels like a complicated approach to what must be a common Rx scenario. Is there an elegant way of solving this?
Marble diagram:
subjA: +---1---2---------------3---------|
subjB: +----------U-------------------------------|
Result: +-----------------------------3---|
I feel like this might be what you're looking for:
IObservable<int> query =
Observable
.Merge(
subjA.Select(a => (int?)a),
subjB.Select(b => (int?)null))
.Throttle(TimeSpan.FromMilliseconds(200.0))
.Where(x => x.HasValue)
.Select(x => x.Value);
query
.Subscribe(s => Console.WriteLine(s));
That's combining both sequences before the Throttle and then only emitting values that come from subjA.
Here is a polished version of Enigmativity's idea to throttle the two sequences after merging them:
static IObservable<TSource> Debounce<TSource, TIgnoreSignal>(
this IObservable<TSource> source,
TimeSpan dueTime,
IObservable<TIgnoreSignal> ignoreDebouncedItem,
IScheduler scheduler = default)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(ignoreDebouncedItem);
if (dueTime < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(dueTime));
scheduler ??= DefaultScheduler.Instance;
return source.Publish(published => published
.Select(x => (x, true))
.Merge(ignoreDebouncedItem
.Select(_ => (default(TSource), false))
.TakeUntil(published.LastOrDefaultAsync())
)
.Throttle(dueTime, scheduler)
.Where(e => e.Item2)
.Select(e => e.Item1));
}
Usage example:
IObservable<int> query = subjA.Debounce(TimeSpan.FromMilliseconds(200), subjB);
I haven't tested it, so it might have bugs.

Dispose previous observable selectmany rx

I'm monitoring a directory with the following setup:
var folder = new Subject();
folder.OnNext("somepath");
folder.SelectMany(FileMonitor)
.Subscribe(x => Console.WriteLine($"Found: {x}"));
public IObservable<string> FileMonitor(string pathToWatch){
return Observable.Create<string>(obs => {
var dfs = CreateAndStartFileWatcher(pathToWatch,obs);
() => dfs.Dispose();
});
}
This works, but if I pass a new path to the subject, the previous FileMonitor is not disposed.
Is there a way to cancel/dispose the previously generated Observable?
It looks like I need: http://reactivex.io/documentation/operators/switch.html but this is not implemented in c#?
Sometimes, asking a question gives yourself new insights.
The solution is to use switch which is available, but only works on a Observable.
So it should be:
var folder = new Subject();
folder.OnNext("somepath");
folder.Select(FileMonitor)
.Switch()
.Subscribe(x => Console.WriteLine($"Found: {x}"));
public IObservable<string> FileMonitor(string pathToWatch){
return Observable.Create<string>(obs => {
var dfs = CreateAndStartFileWatcher(pathToWatch,obs);
() => dfs.Dispose();
});
}
Leaving this question for reference instead of removing it.

Exception handling in RX.Net when using ToEventPattern and Timeout

I am writing some code using RX in C# that must interface with an older system by emitting events.
In summary, I have an observable and need to emit one event when the observable completes and another event if a timeout exception is detected. The main problem is how best to handle the exception.
I'm relatively new to RX, so although I have found a solution, I can't be sure that there isn't a better or more appropriate way that uses the RX extensions better.
This is not the real code but indicates the pattern of my thinking:
public delegate void SuccessHandler(object sender, SuccessEventArgs e);
public event SuccessHandler OnSuccess;
public delegate void TimeoutHandler(object sender, TimeoutEventArgs e);
public event TimeoutHandler OnTimeout;
var id;
var o = Observable.Return() // <- this would be a fetch from an asynchronous source
.Where(r=>r.status=="OK")
.Timeout(new Timespan(0,0,30)
.Do(r=> {
id=r.Id // <-- Ugh! I know this shouldn't be done!
}
.Subscribe(r => {
var statusResponse= new StatusResponse()
{
Id = r.Id
Name = r.Name
Message = "The operation completed successfully",
Status = Status.Success
};
if (OnSuccess == null) return;
OnSuccess (this, new SuccessEventArgs(statusResponse);
},
e =>
{
_logger.LogError(e, "A matching response was not returned in a timely fashion");
if (OnTimeout == null) return;
OnTimeout(this, new TimeoutEventArgs(id));
});
If I didn't need to detect and act upon the timeout it would be fine; I have already worked out how to substitute the Subscribe for ToEventPattern:
...
.Select(r =>
{
var statusResponse= new StatusResponse()
{
Id = r.Id
Name = r.Name
Message = "The operation completed successfully",
Status = Status.Success
};
return new EventPattern<SuccessEventArgs>(this, new SuccessEventArgs(statusResponse));
})
.ToEventPattern();
However, I'd like to be able to detect the timeout (and possibly other exceptions). my experiments with Catch have been unsuccessful because I can't seem to get the types to line up correctly, probably because I don't really understand what is going on.
I'd very much appreciate opinions on this. Is this an acceptable solution? How can I improve it? Can anyone point me to some good online references that will explain how this kind of flow-control and exception handling can be done (all the examples I've seen so far seem to stop short of the real-world case where you want to emit an event and combine that with exception handling).
Thanks in advance
You can branch from observables quite easily, e.g.
var a = Observable.Range(0, 10);
var b = a.Select(x => x * x);
var c = a.Select(x => x * 10);
A word of warning - if the observable is cold, this will cause the producer function to run for each subscription. Look up the difference between hot and cold observables if this isn't clear.
I've created a solution that creates two branches from the source observable and turns each into an event:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var service = new Service();
var apiCall = service.CallApi();
apiCall.OnSuccess.OnNext += (_, __) => Console.WriteLine("Success!");
apiCall.OnTimeout.OnNext += (_, __) => Console.WriteLine("Timeout!");
Console.ReadLine();
}
}
class SuccessEventArgs{}
class TimeoutEventArgs{}
class ApiCall
{
public IEventPatternSource<SuccessEventArgs> OnSuccess {get;}
public IEventPatternSource<TimeoutEventArgs> OnTimeout {get;}
public ApiCall(IEventPatternSource<SuccessEventArgs> onSuccess, IEventPatternSource<TimeoutEventArgs> onTimeout)
{
OnSuccess = onSuccess;
OnTimeout = onTimeout;
}
}
class Service
{
public ApiCall CallApi()
{
var apiCall = Observable
.Timer(TimeSpan.FromSeconds(3))
.Do(_ => Console.WriteLine("Api Called"))
.Select(_ => new EventPattern<SuccessEventArgs>(null, new SuccessEventArgs()))
// .Timeout(TimeSpan.FromSeconds(2)) // uncomment to time out
.Timeout(TimeSpan.FromSeconds(4))
// the following two lines turn the "cold" observable "hot"
// comment them out and see how often "Api Called" is logged
.Publish()
.RefCount();
var success = apiCall
// ignore the TimeoutException and return an empty observable
.Catch<EventPattern<SuccessEventArgs>, TimeoutException>(_ => Observable.Empty<EventPattern<SuccessEventArgs>>())
.ToEventPattern();
var timeout = apiCall
.Materialize() // turn the exception into a call to OnNext rather than OnError
.Where(x => x.Exception is TimeoutException)
.Select(_ => new EventPattern<TimeoutEventArgs>(null, new TimeoutEventArgs()))
.ToEventPattern();
return new ApiCall(success, timeout);
}
}

How to re-subscribe to sequence in particular point?

I'm trying to solve the following:
a) subscriber receives events from IObservable for some time. Then it unsubscribes, do some stuff and then subscribe again. Here it should start receiving events from exactly the same point where unsubscription was performed.
b) Such behavior is desirable for multiple subscribers model. E.g. when one has unsubscribed, others should continue receiving events.
Are there any suggestions from the RX side?
Thanks in advance!
Here's a reasonably simple Rx way to do what you want copied from my answer to this other question. I've created an extension method called Pausable that takes a source observable and a second observable of boolean that pauses or resumes the observable.
public static IObservable<T> Pausable<T>(
this IObservable<T> source,
IObservable<bool> pauser)
{
return Observable.Create<T>(o =>
{
var paused = new SerialDisposable();
var subscription = Observable.Publish(source, ps =>
{
var values = new ReplaySubject<T>();
Func<bool, IObservable<T>> switcher = b =>
{
if (b)
{
values.Dispose();
values = new ReplaySubject<T>();
paused.Disposable = ps.Subscribe(values);
return Observable.Empty<T>();
}
else
{
return values.Concat(ps);
}
};
return pauser.StartWith(false).DistinctUntilChanged()
.Select(p => switcher(p))
.Switch();
}).Subscribe(o);
return new CompositeDisposable(subscription, paused);
});
}
It can be used like this:
var xs = Observable.Generate(
0,
x => x < 100,
x => x + 1,
x => x,
x => TimeSpan.FromSeconds(0.1));
var bs = new Subject<bool>();
var pxs = xs.Pausable(bs);
pxs.Subscribe(x => { /* Do stuff */ });
Thread.Sleep(500);
bs.OnNext(true);
Thread.Sleep(5000);
bs.OnNext(false);
Thread.Sleep(500);
bs.OnNext(true);
Thread.Sleep(5000);
bs.OnNext(false);
It sounds like you need a "pausable" stream. Assuming that only 1 subscriber will handle the values at a time (while the other subscribers just wait), this solution is probably what you need.

Categories