Map errors to observable using C# ReactiveX - c#

I have an observable MyObservable<Object> which can throw CustomExceptions where
private class CustomException : Exception
What I want to do is convert the CustomExceptions into objects and emit those in a new observable.
This is my solution so far but I was wondering if this could be done without having to directly call the Subject's onNext, onCompleted or onError methods.
var MySubject = new Subject<NewObject>();
MyObservable.Catch<Object, CustomException>(
ex =>
{
NewObject o = new NewObject(ex.Message);
MySubject.OnNext(o);
return Observable.Empty<Object>();
});
IObservable<IList<NewObject>> listObservable = MySubject.ToList();
Edit: Thanks ibebbs! Worked like a charm!

You can catch and map exceptions without a subject by using the Materialize() function as shown here:
var errorObservable = source
.Select(projection)
.Materialize()
.Where(notification => notification.Kind == NotificationKind.OnError)
.Select(notification => notification.Exception)
.OfType<CustomException>()
.Select(exception => new NewObject(exception.Message));
The Materialize function takes an IObservable<T> and maps it to a IObservable<Notification<T>> where each Notification has a Kind of OnNext, OnError or OnComplete. The above observable simply looks for Notifications with a Kind`` of OnError and with the Exception being an instance of CustomException then projects these exceptions into anIObservable```.
Here is a unit test showing this working:
[Fact]
public void ShouldEmitErrorsToObservable()
{
Subject<int> source = new Subject<int>();
List<int> values = new List<int>();
List<NewObject> errors = new List<NewObject>();
Func<int, int> projection =
value =>
{
if (value % 2 == 1) throw new CustomException("Item is odd");
return value;
};
Func<CustomException, IObservable<int>> catcher = null;
catcher = ex => source.Select(projection).Catch(catcher);
var errorObservable = source
.Select(projection)
.Materialize()
.Where(notification => notification.Kind == NotificationKind.OnError)
.Select(notification => notification.Exception)
.OfType<CustomException>()
.Select(exception => new NewObject(exception.Message));
var normalSubscription = source.Select(projection).Catch(catcher).Subscribe(values.Add);
var errorSubscription = errorObservable.Subscribe(errors.Add);
source.OnNext(0);
source.OnNext(1);
source.OnNext(2);
Assert.Equal(2, values.Count);
Assert.Equal(1, errors.Count);
}
However, as you can see with the construed catch mechanisms employed above, exception handling in Rx can be tricky to get right and even more difficult to do elegantly. Instead, consider that Exceptions should be Exceptional and, if you expect an class of error such that you've written a custom exception for it, then the error is not really exceptional but part of a process flow that must handle these errors.
In this instance, I would recommend projecting the observable into a class which embodies the "try this operation and record the result, be it a value or an exception" and using this further along the execution chain.
In the example below, I use a "Fallible" class to capture the result or exception of an operation and then subscribe to a stream of "Fallible" instances, separating the errors from values. As you will see, the code is both neater and better performing as both the errors and values share a single subscription to the underlying source:
internal class Fallible
{
public static Fallible<TResult> Try<TResult, TException>(Func<TResult> action) where TException : Exception
{
try
{
return Success(action());
}
catch (TException exception)
{
return Error<TResult>(exception);
}
}
public static Fallible<T> Success<T>(T value)
{
return new Fallible<T>(value);
}
public static Fallible<T> Error<T>(Exception exception)
{
return new Fallible<T>(exception);
}
}
internal class Fallible<T>
{
public Fallible(T value)
{
Value = value;
IsSuccess = true;
}
public Fallible(Exception exception)
{
Exception = exception;
IsError = true;
}
public T Value { get; private set; }
public Exception Exception { get; private set; }
public bool IsSuccess { get; private set; }
public bool IsError { get; private set; }
}
[Fact]
public void ShouldMapErrorsToFallible()
{
Subject<int> source = new Subject<int>();
List<int> values = new List<int>();
List<NewObject> errors = new List<NewObject>();
Func<int, int> projection =
value =>
{
if (value % 2 == 1) throw new CustomException("Item is odd");
return value;
};
var observable = source
.Select(value => Fallible.Try<int, CustomException>(() => projection(value)))
.Publish()
.RefCount();
var errorSubscription = observable
.Where(fallible => fallible.IsError)
.Select(fallible => new NewObject(fallible.Exception.Message))
.Subscribe(errors.Add);
var normalSubscription = observable
.Where(fallible => fallible.IsSuccess)
.Select(fallible => fallible.Value)
.Subscribe(values.Add);
source.OnNext(0);
source.OnNext(1);
source.OnNext(2);
Assert.Equal(2, values.Count);
Assert.Equal(1, errors.Count);
}
Hope it helps.

Related

More concise/idiomatic way of continuing with code if true is returned or exiting if false

I have a few items -- let's just call them itemA, itemB, itemC, and itemD -- and I would like to validate each of them using a validation method I have written.
The validation method return type and signature is as follows:
public async Task<ValidationMessage> ValidateItem(MyClass item);
The ValidationMessage is a simple class:
public class ValidationMessage
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
To validate each item, currently I have the following code:
ValidationMessage result = new ValidationMessage();
result = await this.ValidateItem(itemA);
if (!result.Success)
{
return result;
}
result = await this.ValidateItem(itemB);
if (!result.Success)
{
return result;
}
result = await this.ValidateItem(itemC);
if (!result.Success)
{
return result;
}
result = await this.ValidateItem(itemD);
return result;
As you can see, as soon as one of the items fails the validation method (meaning result.Success == false), I return and do not continue with validating the rest of them.
I think it's kind of tedious/ugly to have the repeated assignments to result and the repeated if statements. I was hoping there is some existing c# class/construct (perhaps LINQ can help) to write this more concisely. I made up the following to demonstrate what I'm sort of thinking:
ValidationMessage result = new ValidationMessage();
result = await this.ValidateItem(itemA).ContinueConditional(
(r => r.Success) => await this.ValidateItem(itemB).ContinueConditional(
(r => r.Success) => await this.ValidateItem(itemC).ContinueConditional(
(r => r.Success) => await this.ValidateItem(itemD))));
return result;
Basically, the return value of this.ValidateItem(itemA) is assigned to result and then this value goes into the ContinueConditional. If the result, r, has Success == true, then continue with validating itemB, and so on. If it's not successful, then exit out of that code and go straight to the return statement at the bottom, thus returning the item which failed the validation. If all items are validated then it will go to that return statement anyway.
I apologize for the long wall of text, especially if the c# construct already exists and is obvious. I appreciate any help with this.
Note: I have simplified my real example for the sake of posting this. One important part of the simplification is that itemA, itemB, itemC, and itemD are not the same type, despite referring to them as MyClass in the method signature above. So I can't just put them in a list and use LINQ. This also means I actually have different validation methods to accept the various items I have, but I didn't want to explain all that at the top in case it over-complicates things. The important part is that they all return ValidationMessage.
What about inverting the logic around the check for success/failure?
ValidationMessage result = await this.ValidateItem(itemA);
if (result.Success) result = await this.ValidateItem(itemB);
if (result.Success) result = await this.ValidateItem(itemC);
if (result.Success) result = await this.ValidateItem(itemD);
return result;
How about this? You can extend it to as many elements as you want.
var validationRules = new List<Func<Task<bool>>>(){
() => ValidateItem(itemA),
() => ValidateItem(itemB),
() => ValidateItem(itemC),
() => ValidateItem(itemD),
};
ValidationMessage result = new ValidationMessage();
foreach(var validationRule in validationRules)
{
result = await validationRule();
if(!result)
return result;
}
return result;
You could use FluentValidation to create a custom rule to loop through a list of models to validate. This is more overhead than the other answers but it's a good approach with clean readable code.
First define your shared model
public class YourSharedModel()
{
List<MyClass> Models = new List<MyClass>();
}
Define the validator:
public class SharedModelValidator : AbstractValidator<YourSharedModel>
{
public SharedModelValidator()
{
CustomRule(BeValid)
}
public bool BeValid(ValidationErrors<YourSharedModel> validationFailures, YourSharedModel sharedModel, ValidationContext<YourSharedModel> validationContext)
{
for (var m in sharedModel.Models)
{
var result = YourValidationMethod(m);
if (!result.Success)
{
validationFailures.AddFailureFor(x => m, result.ErrorMessage);
return false;
}
}
return true;
}
private YourValidationMethod(MyClass model)
{
// the code you have that actually validates
}
}
Implementation would be something like:
var yourSharedModel = new YourSharedModel();
yourSharedModel.Models.Add(itemA);
yourSharedModel.Models.Add(itemB);
// etc
var validator = new SharedModelValidator();
var results = validator.Validate(yourSharedModel);
if (!results.IsValid)
{
// do something with results.Errors
}
You could also keep all your errors and return false after the loop so that you get all the errors at once rather than one by one on submission, of course that's assuming a form post from a web application. Makes for a better user friendly experience.
You could use an interface and then add your items to a list that you foreach through:
ValidationMessage Test()
{
List<IValidatable> items = new List<IValidatable>();
MyClass1 item1 = new MyClass1();
MyClass2 item2 = new MyClass2();
items.Add(item1);
items.Add(item2);
ValidationMessage result = null;
foreach (var i in items)
{
result = i.ValidateItem();
if (!result.Success) break;
}
return result;
}
interface IValidatable
{
ValidationMessage ValidateItem();
}
public class ValidationMessage
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
public class MyClass1 : IValidatable
{
public ValidationMessage ValidateItem()
{
return new ValidationMessage();
}
}
public class MyClass2 : IValidatable
{
public ValidationMessage ValidateItem()
{
return new ValidationMessage();
}
}
Another (perhaps anorthodox) way to do it is to overload the && operator on ValidationMessage:
public class ValidationMessage
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
public static ValidationMessage operator &(ValidationMessage message1, ValidationMessage message2)
{
return message1.Success ? message2 : message1;
}
public static ValidationMessage operator |(ValidationMessage message1, ValidationMessage message2)
{
return message1.Success ? message1 : message2;
}
public static bool operator true(ValidationMessage message)
{
return message.Success;
}
public static bool operator false(ValidationMessage message)
{
return !message.Success;
}
}
Then you can do:
return (await this.ValidateItem(ItemA)) &&
(await this.ValidateItem(ItemB)) &&
(await this.ValidateItem(ItemC)) &&
(await this.ValidateItem(ItemD));

How to use .Net IObservable::Retry with WhenAnyValue from ReactiveUI

If I have an INPC supporting class Numbers with two properties A and B. I can write code like
Numbers numbers = new Numbers();
IObservable<double> o = numbers.WhenAnyValue(p=>p.A,p=>p.B,(a,b)=>a/b);
WhenAnyValue is a utility method in the ReactiveUI library for composing observables from property change events. If I then write.
o.Subscribe(v=>Console.WriteLine(v));
it will print a/b whenever A or B changes. This is all good until I set
numbers.B = 0;
Now a/b will throw a DivideByZeroException and the observable will terminate. However this is a UI. I don't want the observable to terminate. I just either wish to ignore the exception or log it and move on. First attempt is to see that IObservable contains an extension method called Retry which will reconnect to the observable after an exception. We try
Numbers numbers = new Numbers();
IObservable<double> o = numbers
.WhenAnyValue(p=>p.A,p=>p.B,(a,b)=>a/b)
.Retry();
o.Subscribe(v=>Console.WriteLine(v));
However when I do numbers.B = 0 then the Retry will ignore the exception and reconnect and will immediately fail again and again and again because WhenAnyValue always delivers an event on subscription.
So it seems what I need is a Retry that will ignore the first input after reconnection iff it is the same as the input that caused the error that disconnected the first one except I don't think this is possible with RX.
Any ideas?
Full Test Case
The below test case does not terminate.
public class Numbers : ReactiveObject
{
int _A;
public int A
{
get { return _A; }
set { this.RaiseAndSetIfChanged(ref _A, value); }
}
int _B;
public int B
{
get { return _B; }
set { this.RaiseAndSetIfChanged(ref _B, value); }
}
}
[Fact]
public void TestShouldTerminate()
{
var numbers = new Numbers();
var o = numbers
.WhenAnyValue(p => p.A, p => p.B, Tuple.Create)
.Select(v=>v.Item1/v.Item2)
.Select(v=>v+1)
.Retry();
double value = 0;
o.Subscribe(v => value = v);
numbers.A = 10;
numbers.B = 20;
value.Should().Be(1.5);
}
}
}
}
This can't be handled in vanilla RX. I've created a wrapper called
IObservableExceptional
IObserverExceptional
that changes the standard contract for error handling in RX. Errors now no longer terminate the observable. It supports LINQ and should be fairly transparent for most uses. The test case that passes is
[Fact]
public void ErrorsCanBePropogated()
{
var numbers = new Numbers();
var list = new List<double>();
var errors = new List<Exception>();
numbers
.WhenAnyValue(p => p.A, p => p.B, Tuple.Create)
.ToObservableExceptional()
.Select(v => v.Item1/v.Item2)
.Subscribe(onNext: val=>list.Add(val), onError:err=>errors.Add(err));
list.Count.Should().Be(0);
errors.Count.Should().Be(1);
numbers.A = 10;
list.Count.Should().Be(0);
errors.Count.Should().Be(2);
numbers.B = 5;
list.Count.Should().Be(1);
list[0].Should().Be(2.0);
errors.Count.Should().Be(2);
}
The three new interfaces are
public interface IObservableExceptional<T>
{
void Subscribe(IObserverExceptional<T> observer);
IObservable<IExceptional<T>> Observable { get; }
}
public interface IObserverExceptional<T>
{
void OnNext(IExceptional<T> t);
void OnCompleted();
IObserver<IExceptional<T>> Observer { get; }
}
public interface IExceptional<out T> : IEnumerable<T>
{
bool HasException { get; }
Exception Exception { get; }
T Value { get; }
string ToMessage();
void ThrowIfHasException();
}
IObservableException and IExceptional both support LINQ ( ie they are Monads )
Any exceptions thrown within the Select or SelectMany combinators of IObservableExceptional are wrapped as IExceptional objects and passed on to the subscriber. An error does not terminate the subscription.
The repository is at
https://github.com/Weingartner/Exceptional

Saving subscription state for resuming it later

Update - solved
The final solution differs a bit from Brandon's suggestion but his answer brought me on the right track.
class State
{
public int Offset { get; set; }
public HashSet<string> UniqueImageUrls = new HashSet<string>();
}
public IObservable<TPicture> GetPictures(ref object _state)
{
var localState = (State) _state ?? new State();
_state = localState;
return Observable.Defer(()=>
{
return Observable.Defer(() => Observable.Return(GetPage(localState.Offset)))
.SubscribeOn(TaskPoolScheduler.Default)
.Do(x=> localState.Offset += 20)
.Repeat()
.TakeWhile(x=> x.Count > 0)
.SelectMany(x=> x)
.Where(x=> !localState.UniqueImageUrls.Contains(x.ImageUrl))
.Do(x=> localState.UniqueImageUrls.Add(x.ImageUrl));
});
}
IList<TPicture> GetPage(int offset)
{
...
return result;
}
Original Question
I'm currently struggling with the following problem. The PictureProvider implementation shown below is working with an offset variable used for paging results of a backend service providing the actual data. What I would like to implement is an elegant solution making the current offset available to the consumer of the observable to allow for resuming the observable sequence at a later time at the correct offset. Resuming is already accounted for by the intialState argument to GetPictures().
Recommendations for improving the code in a more RX like fashion would be welcome as well. I'm actually not so sure if the Task.Run() stuff is appropriate here.
public class PictureProvider :
IPictureProvider<Picture>
{
#region IPictureProvider implementation
public IObservable<Picture> GetPictures(object initialState)
{
return Observable.Create<Picture>((IObserver<Picture> observer) =>
{
var state = new ProducerState(initialState);
ProducePictures(observer, state);
return state;
});
}
#endregion
void ProducePictures(IObserver<Picture> observer, ProducerState state)
{
Task.Run(() =>
{
try
{
while(!state.Terminate.WaitOne(0))
{
var page = GetPage(state.Offset);
if(page.Count == 0)
{
observer.OnCompleted();
break;
}
else
{
foreach(var picture in page)
observer.OnNext(picture);
state.Offset += page.Count;
}
}
}
catch (Exception ex)
{
observer.OnError(ex);
}
state.TerminateAck.Set();
});
}
IList<Picture> GetPage(int offset)
{
var result = new List<Picture>();
... boring web service call here
return result;
}
public class ProducerState :
IDisposable
{
public ProducerState(object initialState)
{
Terminate = new ManualResetEvent(false);
TerminateAck = new ManualResetEvent(false);
if(initialState != null)
Offset = (int) initialState;
}
public ManualResetEvent Terminate { get; private set; }
public ManualResetEvent TerminateAck { get; private set; }
public int Offset { get; set; }
#region IDisposable implementation
public void Dispose()
{
Terminate.Set();
TerminateAck.WaitOne();
Terminate.Dispose();
TerminateAck.Dispose();
}
#endregion
}
}
I suggest refactoring your interface to yield the state as part of the data. Now the client has what they need to resubscribe where they left off.
Also, once you start using Rx, you should find that using synchronization primitives like ManualResetEvent are rarely necessary. If you refactor your code so that retrieving each page is its own Task, then you can eliminate all of that synchronization code.
Also, if you are calling a "boring web service" in GetPage, then just make it async. This gets rid of the need to call Task.Run among other benefits.
Here is a refactored version, using .NET 4.5 async/await syntax. It could also be done without async/await. I also added a GetPageAsync method that uses Observable.Run just in case you really cannot convert your webservice call to be asynchronous
/// <summary>A set of pictures</summary>
public struct PictureSet
{
public int Offset { get; private set; }
public IList<Picture> Pictures { get; private set; }
/// <summary>Clients will use this property if they want to pick up where they left off</summary>
public int NextOffset { get { return Offset + Pictures.Count; } }
public PictureSet(int offset, IList<Picture> pictures)
:this() { Offset = offset; Pictures = pictures; }
}
public class PictureProvider : IPictureProvider<PictureSet>
{
public IObservable<PictureSet> GetPictures(int offset = 0)
{
// use Defer() so we can capture a copy of offset
// for each observer that subscribes (so multiple
// observers do not update each other's offset
return Observable.Defer<PictureSet>(() =>
{
var localOffset = offset;
// Use Defer so we re-execute GetPageAsync()
// each time through the loop.
// Update localOffset after each GetPageAsync()
// completes so that the next call to GetPageAsync()
// uses the next offset
return Observable.Defer(() => GetPageAsync(localOffset))
.Select(pictures =>
{
var s = new PictureSet(localOffset, pictures);
localOffset += pictures.Count;
})
.Repeat()
.TakeWhile(pictureSet => pictureSet.Pictures.Count > 0);
});
}
private async Task<IList<Picture>> GetPageAsync(int offset)
{
var data = await BoringWebServiceCallAsync(offset);
result = data.Pictures.ToList();
}
// this version uses Observable.Run() (which just uses Task.Run under the hood)
// in case you cannot convert your
// web service call to be asynchronous
private IObservable<IList<Picture>> GetPageAsync(int offset)
{
return Observable.Run(() =>
{
var result = new List<Picture>();
... boring web service call here
return result;
});
}
}
Clients just need to add a SelectMany call to get their IObservable<Picture>. They can choose to store the pictureSet.NextOffset if they wish.
pictureProvider
.GetPictures()
.SelectMany(pictureSet => pictureSet.Pictures)
.Subscribe(picture => whatever);
Instead of thinking about how to save the subscription state, I would think about how to replay the state of the inputs (i.e. I'd try to create a serializable ReplaySubject that, on resume, would just resubscribe and catch back up to the current state).

Moq is slow to verify dependency after a large number calls

I've hit a snag when using Moq to simulate an dependency which is called a large number of times. When I call Verify, Moq takes a long time (several minutes) to respond, and sometimes crashes with a NullReferenceException (I guess this is understandable, given the amount of data that Moq would have to accumulate to do the Verify from a "cold start").
So my question is, is there another strategy that I can use to do this using Moq, or should I revert to a hand-crafted stub for this rather unusual case. Specifically, is there a way to tell Moq up front that I'm only interested in verifying specific filters on the parameters, and to ignore all other values?
Neither of the approaches below is satisfactory.
Given CUT and Dep:
public interface ISomeInterface
{
void SomeMethod(int someValue);
}
public class ClassUnderTest
{
private readonly ISomeInterface _dep;
public ClassUnderTest(ISomeInterface dep)
{
_dep = dep;
}
public void DoWork()
{
for (var i = 0; i < 1000000; i++) // Large number of calls to dep
{
_dep.SomeMethod(i);
}
}
}
Moq Strategy 1 - Verify
var mockSF = new Mock<ISomeInterface>();
var cut = new ClassUnderTest(mockSF.Object);
cut.DoWork();
mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)),
Times.Once());
mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)),
Times.Never());
Moq Strategy 2 - Callback
var mockSF = new Mock<ISomeInterface>();
var cut = new ClassUnderTest(mockSF.Object);
bool isGoodValueAlreadyUsed = false;
mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)))
.Callback(() =>
{
if (isGoodValueAlreadyUsed)
{
throw new InvalidOperationException();
}
isGoodValueAlreadyUsed = true;
});
mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)))
.Callback(() =>
{ throw new InvalidOperationException(); });
cut.DoWork();
Assert.IsTrue(isGoodValueAlreadyUsed);
Usually when such a limitation is reached, I would reconsider my design (no offense, I see your rep). Looks like the method under test does too much work, which is violation of the single responsibility principle. It first generates a large list of items, and then verifies a worker is called for each one of them, while also verifying that the sequence contains the right elements.
I'd split the functionality into a sequence generator, and verify that the sequence has the right elements, and another method which acts on the sequence, and verify that it executes the worker for each element:
namespace StackOverflowExample.Moq
{
public interface ISequenceGenerator
{
IEnumerable<int> GetSequence();
}
public class SequenceGenrator : ISequenceGenerator
{
public IEnumerable<int> GetSequence()
{
var list = new List<int>();
for (var i = 0; i < 1000000; i++) // Large number of calls to dep
{
list.Add(i);
}
return list;
}
}
public interface ISomeInterface
{
void SomeMethod(int someValue);
}
public class ClassUnderTest
{
private readonly ISequenceGenerator _generator;
private readonly ISomeInterface _dep;
public ClassUnderTest(ISomeInterface dep, ISequenceGenerator generator)
{
_dep = dep;
_generator = generator;
}
public void DoWork()
{
foreach (var i in _generator.GetSequence())
{
_dep.SomeMethod(i);
}
}
}
[TestFixture]
public class LargeSequence
{
[Test]
public void SequenceGenerator_should_()
{
//arrange
var generator = new SequenceGenrator();
//act
var list = generator.GetSequence();
//assert
list.Should().Not.Contain(-1);
Executing.This(() => list.Single(i => i == 12345)).Should().NotThrow();
//any other assertions
}
[Test]
public void DoWork_should_perform_action_on_each_element_from_generator()
{
//arrange
var items = new List<int> {1, 2, 3}; //can use autofixture to generate random lists
var generator = Mock.Of<ISequenceGenerator>(g => g.GetSequence() == items);
var mockSF = new Mock<ISomeInterface>();
var classUnderTest = new ClassUnderTest(mockSF.Object, generator);
//act
classUnderTest.DoWork();
//assert
foreach (var item in items)
{
mockSF.Verify(c=>c.SomeMethod(item), Times.Once());
}
}
}
}
EDIT:
Different approaches can be mixed to define a specific expectations, incl. When(), the obsoleted AtMost(), MockBehavior.Strict, Callback, etc.
Again, Moq is not designed to work on large sets, so there is performance penalty. You are still better off using another measures to verify what data will be passed to the mock.
For the example in the OP, here is a simplified setup:
var mockSF = new Mock<ISomeInterface>(MockBehavior.Strict);
var cnt = 0;
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i != -1)));
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i == 12345))).Callback(() =>cnt++).AtMostOnce();
This will throw for -1, for more than one invocation with 12, and assertion can be made on cnt != 0.

Prioritized subscriptions invocation

I have a single observable sequence of messages. There is a set of subscribers that can handle these messages. Each subscriber has an execution priority. Each message must be handled once by the highest priority subscriber chosen from the list of currently subscribed subscribers. Subscribers are constantly subscribed/unsubscribed from the sequence, so we don't know the number and priorities of subscribers when constructing the sequence. Is it a possible/viable solution using rx?
To illustrate:
public class Message
{
public string Value { get; set; }
public bool IsConsumed { get; set; }
}
var subject = new Subject<Message>();
var sequence = subject.Publish().RefCount();
Action<Message, int> subscriber = (m, priority) =>
{
if (!m.IsConsumed)
{
m.IsConsumed = true;
Trace.WriteLine(priority);
}
};
var s2 = sequence.Priority(2).Subscribe(m => subscriber(m, 2));
var s1 = sequence.Priority(1).Subscribe(m => subscriber(m, 1));
subject.OnNext(new Message()); // output: 1
s1.Dispose();
subject.OnNext(new Message()); // output: 2
The missing piece to make this solution work is the Priority method which do not exist in Rx library.
This was a very interesting problem...
So, first off: I am not aware of any intrinsic Rx operators that can achieve a "routing" effect similar to what you want in this Priority extension.
That said, I was playing around in LINQPad over lunch today, and came up with a (very) hacky proof of concept that appears to work:
First, your message class
public class Message
{
public string Value { get; set; }
public bool IsConsumed { get; set; }
}
Next, the extension method wrapper-class:
public static class Ext
{
public static PrioritizedObservable<T> Prioritize<T>(this IObservable<T> source)
{
return new PrioritizedObservable<T>(source);
}
}
And what is this PrioritizedObservable<T>?
public class PrioritizedObservable<T>
: IObservable<T>, IObserver<T>, IDisposable
{
private IObservable<T> _source;
private ISubject<T,T> _intermediary;
private IList<Tuple<int, Subject<T>>> _router;
public PrioritizedObservable(IObservable<T> source)
{
// Make sure we don't accidentally duplicate subscriptions
// to the underlying source
_source = source.Publish().RefCount();
// A proxy from the source to our internal router
_intermediary = Subject.Create(this, _source);
_source.Subscribe(_intermediary);
// Holds per-priority subjects
_router = new List<Tuple<int, Subject<T>>>();
}
public void Dispose()
{
_intermediary = null;
foreach(var entry in _router)
{
entry.Item2.Dispose();
}
_router.Clear();
}
private ISubject<T,T> GetFirstListener()
{
// Fetch the first subject in our router
// ordered by priority
return _router.OrderBy(tup => tup.Item1)
.Select(tup => tup.Item2)
.FirstOrDefault();
}
void IObserver<T>.OnNext(T value)
{
// pass along value to first in line
var nextListener = GetFirstListener();
if(nextListener != null)
nextListener.OnNext(value);
}
void IObserver<T>.OnError(Exception error)
{
// pass along error to first in line
var nextListener = GetFirstListener();
if(nextListener != null)
nextListener.OnError(error);
}
void IObserver<T>.OnCompleted()
{
var nextListener = GetFirstListener();
if(nextListener != null)
nextListener.OnCompleted();
}
public IDisposable Subscribe(IObserver<T> obs)
{
return PrioritySubscribe(1, obs);
}
public IDisposable PrioritySubscribe(int priority, IObserver<T> obs)
{
var sub = new Subject<T>();
var subscriber = sub.Subscribe(obs);
var entry = Tuple.Create(priority, sub);
_router.Add(entry);
_intermediary.Subscribe(sub);
return Disposable.Create(() =>
{
subscriber.Dispose();
_router.Remove(entry);
});
}
}
And a test harness:
void Main()
{
var subject = new Subject<Message>();
var sequence = subject.Publish().RefCount().Prioritize();
Action<Message, int> subscriber = (m, priority) =>
{
if (!m.IsConsumed)
{
m.IsConsumed = true;
Console.WriteLine(priority);
}
};
var s3 = sequence.PrioritySubscribe(3, Observer.Create<Message>(m => subscriber(m, 3)));
var s2 = sequence.PrioritySubscribe(2, Observer.Create<Message>(m => subscriber(m, 2)));
var s1 = sequence.PrioritySubscribe(1, Observer.Create<Message>(m => subscriber(m, 1)));
var s11 = sequence.PrioritySubscribe(1, Observer.Create<Message>(m => subscriber(m, 1)));
subject.OnNext(new Message()); // output: 1
s1.Dispose();
subject.OnNext(new Message()); // output: 1
s11.Dispose();
subject.OnNext(new Message()); // output: 2
s2.Dispose();
subject.OnNext(new Message()); // output: 3
sequence.Dispose();
}

Categories