In a recent practice to learn ReactiveUI, I wrote a simple timer module to
create a countdown UI feature. To keep the UI responsive, I introduced some multi-tasking code which is provided below. However the code is not functioning as expected, more specifically not running on the expect thread/scheduler.
I created The timeoutObservable to produce a series of TimeSpan objects, then I made a subscription to it with a simple lambda expression which changes a property bound to a UI textblock control. I used SubscribeOn(RxApp.MainThreadScheduler) to ensure the subscription code run on the main/dispatcher thread.
WndMainVm.cs
public class WndMainVm : ReactiveObject
{
public WndMainVm()
{
ButtonDisplayString = $"Play! (Timeout: {GameTimeout.TotalSeconds}s)";
StartGameCommand = ReactiveCommand.CreateFromTask(async _ =>
{
IsGameStarted = true;
TimeLeft = GameTimeout;
var lastRecordTime = DateTime.Now;
await GameControlInteraction.StartGame.Handle(Unit.Default);
var timeoutObservable = Observable
.Interval(UpdateInterval)
.Select(l =>
{
var newLastRecordTime = DateTime.Now;
var newTimeLeft = TimeLeft - (newLastRecordTime - lastRecordTime);
lastRecordTime = newLastRecordTime;
return newTimeLeft;
})
.Merge(Observable
.Timer(GameTimeout)
.Select(l => TimeSpan.Zero))
.TakeUntil(ts => ts == TimeSpan.Zero);
timeoutObservable.
SubscribeOn(RxApp.MainThreadScheduler).
Subscribe(ts =>
TimeLeft = ts);
await timeoutObservable;
await GameControlInteraction.StopGame.Handle(Unit.Default);
IsGameStarted = false;
}, this.WhenAnyValue(x => x.IsGameStarted).Select(v => !v));
this.WhenAnyValue(x => x.TimeLeft)
.Select(v => $"Time left: {v.TotalMilliseconds}ms")
.ToProperty(this, x => x.TimeoutDisplayString, out _timeoutDisplayString, scheduler: RxApp.MainThreadScheduler);
}
private readonly ObservableAsPropertyHelper<string> _timeoutDisplayString;
public TimeSpan GameTimeout { get; } = TimeSpan.FromSeconds(10);
public TimeSpan UpdateInterval { get; } = TimeSpan.FromMilliseconds(10);
[Reactive]
public bool IsGameStarted { get; set; }
[Reactive]
public TimeSpan TimeLeft { get; set; }
[Reactive]
public string ButtonDisplayString { get; set; }
public string TimeoutDisplayString => _timeoutDisplayString.Value;
public ReactiveCommand<Unit, Unit> StartGameCommand { get; }
}
WndMain.cs
public partial class WndMain : ReactiveWindow<WndMainVm>
{
public WndMain()
{
InitializeComponent();
ViewModel = new WndMainVm();
this.WhenActivated(d =>
{
this.OneWayBind(ViewModel, x => x.ButtonDisplayString, x => x.BtnPlayStop.Content).DisposeWith(d);
this.OneWayBind(ViewModel, x => x.TimeoutDisplayString, x => x.TbkTimeDisplay.Text).DisposeWith(d); \\CountDownDisplay
this.BindCommand(ViewModel, x => x.StartGameCommand, x => x.BtnPlayStop).DisposeWith(d);
});
}
}
However when I was testing the code, I found out that the subscription code is always running on a thread from thread pool, causing a TargetInvocationException. I know this would happen when you try to change a property of a control from a thread other than main/dispatcher thread, so I wonder if there is something wrong with my code preventing it from executing on the right thread. Currently I tried to bypass this problem by creating a dependent property TimeoutDisplayString and it worked fine, but this problem still bewilders me and I really want to find out why.
I'm not very familiar with the async/await keyword, so my guess is that I didn't use them correctly, can anyone kindly have a look at it, point out my error, or provide a better countdown solution?
Generally when we think we should use SubscribeOn we should actually be using ObserveOn.
The SubscribeOn operator is similar, but it instructs the Observable to itself operate on the specified Scheduler, as well as notifying its observers on that Scheduler.
http://reactivex.io/documentation/operators/observeon.html
Related
I am making my first steps with ReactiveUI but I am not able to event get a very basic example to work. I want to execute an task as soon as the property "SearchTerm" changes. I followed the instructions on the github page of ReactiveUI ("a compelling example").
I have a ViewModel with the property SearchTerm which is bind to a TextBox in my view. If I update the content of the TextBox the property is updated as expected (I used UpdateSourceTrigger=PropertyChanged).
The code in my observables never fires:
public class MainWindowViewModel: ReactiveObject
{
public string SearchTerm
{
get { return m_SearchTerm; }
set { this.RaiseAndSetIfChanged(ref m_SearchTerm, value); }
}
private string m_SearchTerm;
public MainWindowViewModel()
{
SearchResults = new List<string>();
var canSearch = this.WhenAny(x => x.SearchTerm, x => !string.IsNullOrWhiteSpace(x.GetValue()));
var search = ReactiveCommand.CreateAsyncTask(canSearch,
async _ => {
// this is never called
return await dosearch(this.SearchTerm);
});
search.Subscribe(results => {
// this is never called too
SearchResults.Clear();
SearchResults.AddRange(results);
});
}
private async Task<List<string>> dosearch(string searchTerm)
{
await Task.Delay(1000);
return new List<string>() { "1", "2", "3" };
}
public List<string> SearchResults { get; private set; }
}
The code inside your command never fires, because you are not invoking the command anywhere. You have to bind your command to any event (like input text changing, button click, etc).
First, expose your command from ViewModel:
public ReactiveCommand<List<string>> Search { get; private set; }
next, assign it in constructor:
this.Search = ReactiveCommand.CreateAsyncTask(canSearch,
async _ => {
return await dosearch(this.SearchTerm);
});
and finally, invoke the command when the input changes (this is the crucial missing part of your code):
this.WhenAnyValue(x => x.SearchTerm)
.InvokeCommand(this, x => x.Search);
Put the abouve code in the constructor.
Note that this will fire searches constantly when the user types. To fix this, you can use an Rx operator called Throttle, as seen in the example you linked to.
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
I am developing a windows 8 store app, using C#/xaml, with the MVVM pattern.
I want to refresh a page automatically every 30 seconds, after a search I came up with this, but how do I implement it into a MVVM page?
Edit: As of Charleh's answer I came up with this:
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
var period = TimeSpan.FromSeconds(30);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RefreshOrdersList();
});
}, period);
But the VS compiler marks an 'Error' under the dispatcher.RunAsync() function: "The 'await' operator can only be used within an async lambda expression". When I remove the 'await' keyword the application runs with a warning saying "because this call is not awaited, execution of the current method continues before the call is completed, Consider apply the 'await' operator to the result of the call".
RefreshOrdersList function -gets all orders and details from WCF service:
private async void RefreshOrdersList()
{
var orders = await proxy.GetAllOrdersAsync();
IList<OrderCart> orderModel = new List<OrderCart>();
foreach (var order in orders)
{
OrderCart oc = new OrderCart { Id = order.Id, FullPrice = Convert.ToDouble(order.Total), TableId = order.TableId, IsDone = false, OrderTime = (DateTime)order.StartOrderTime };
order.OrderDetails = await proxy.GetOrderDetailsByOrderIdAsync(order.Id);
oc.OrderCartItems = new ObservableCollection<OrderCartItem>();
foreach (var orderDetail in order.OrderDetails)
{
var course = await proxy.GetCourseByIdAsync(orderDetail.CourseId);
OrderCartItem oci = new OrderCartItem { Quantity = orderDetail.Amount, Course = new Course(course) };
oc.OrderCartItems.Add(oci);
}
orderModel.Add(oc);
}
var sortOrder = orderModel.OrderByDescending(x => x.Id);
Items = new ObservableCollection<OrderCartViewModel>(sortOrder.
Select(o => new OrderCartViewModel(o))
.ToList());
Items property:
public ObservableCollection<OrderCartViewModel> Items
{
get { return _items; }
private set { SetProperty(ref _items, value); }
}
Anyone??
The dispatcher is found on UI types (controls etc) and it's job is to sync to the UI thread so you will only find this in your views.
Is there any reason the whole view needs to reload?
If you are using the MVVM pattern, you can just do your work asyncronously in the viewmodel, and then update your properties as per usual.
e.g.
class SomeViewModel
{
IEnumerable<Results> Results { get; set; } // Obviously this would actually need to raise PropertyChanged from INotifyPropertyChanged in order to refresh any bindings
public SomeViewModel()
{
var period = TimeSpan.FromMinutes(1);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// do your query/work here
DoSomeWorkAsync();
},
period);
}
void DoSomeWorkAsync()
{
// Get the data
someService.GetResults((result) => { Results = result.Data; });
}
}
The binding system will take care of the UI update. I assume you just want to go off to the datasource (a web service maybe?) and get new results every 30 seconds, do you need more than just a data/binding update?
Disclaimer: Didn't test this, and you'd also need to be aware of exceptions thrown within the thread pool work item and handle appropriately
Edit: In response to your question about the binding system
The WPF/RT/SL binding system looks for data sources that implement certain interfaces - one of these is INotifyPropertyChanged. As long as the datasource you are binding to implements this (and in this case your datasource is the viewmodel), and you raise a PropertyChanged event for the property when it changes, the binding system will know to refresh
I have a code snippet in Visual Studio (well actually Resharper as it works a bit better) that writes the property for me - I think there is one included in VS but it should look something like this:
private int _someInt;
public int SomeInt
{
get { return _someInt; }
set
{
if(_someInt != value)
{
_someInt = value;
// Helper function in the class which checks to see if propertychanged has any subscribers
OnPropertyChanged("_someInt");
}
}
}
Note: There are better implementations of this using Expressions instead of 'magic strings' or using new language features. If you are using the latest c# version the addition of CallerMemberName attribute means you don't need to specify the property name
Edit 2: Ok a complete example may look like this
public class SomeViewModel : INotifyPropertyChanged
{
#region Properties (that support INotifyPropertyChanged)
private IEnumerable<Result> _results;
public IEnumerable<Result> Results
{
get
{
return _results;
}
set
{
if (value != _results)
{
_results = value;
OnPropertyChanged("Results");
}
}
}
#endregion
// A reference to the service that will get some data
private IDataService _dataService;
public SomeViewModel(IDataService dataService)
{
_dataService = dataService;
var period = TimeSpan.FromMinutes(1);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// do your query/work here
GetData();
},
period);
}
#region Data fetch method
/// <summary>
/// Method to get some data - waits on a service to return some results
/// </summary>
void GetData()
{
// Get the data
_dataService.GetResults((result) =>
{
// Try this instead
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.Invoke(() => {
Results = result.Data; }
});
}
#endregion
#region INotifyPropertyChanged
// Note: usually most MVVM frameworks have a base class which implements the INPC interface for you (such as PropertyChangedBase in Caliburn Micro)
// so it might be worth fishing around in your framework for it if you are using one.. If you aren't using a framework then you are a braver man than me
// start using one now!
/// <summary>
/// The property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The helper function to raise the event
/// </summary>
/// <param name="propertyName">Name of the property that changed (to tell the binding system which control to update on screen based on bindings)</param>
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Edit: update code above. Let me know if you can't get hold of the dispatcher still
Just need to add the 'async' keyword var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source)=>, and no errors or warning were detected:
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
var period = TimeSpan.FromSeconds(30);
var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source) =>
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RefreshOrdersList();
});
}, period);
Suppose I have the following class:
public class Person : ReactiveObject, IEditableObject
{
private string name;
private string nameCopy;
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.name = this.nameCopy;
}
public void EndEdit()
{
}
}
Now suppose I want to create an observable sequence (of Unit) that "ticks" whenever a change is committed to Name. That is, I only care about changes to Name that occur between a call to BeginEdit and and a subsequent call to EndEdit. Any changes prior to a call to CancelEdit should be ignored and the sequence should not tick.
I'm struggling to get my head around how I would do this with Rx. It seems I would need state in the pipeline somewhere in order to know whether the change occurred during the window of BeginEdit/EndEdit calls. I suppose I could timestamp everything and compare timestamps, but that seems a nasty hack.
I came pretty close using a dedicated Subject for edit actions along with Observable.Merge:
public class Person : ReactiveObject, IEditableObject
{
private readonly Subject<EditAction> editActions;
private readonly IObservable<Unit> changedDuringEdit;
private string name;
private string nameCopy;
public Person()
{
this.editActions = new Subject<EditAction>();
var nameChanged = this.ObservableForProperty(x => x.Name).Select(x => x.Value);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
this.changedDuringEdit = nameChanged
.Buffer(editBeginning, _ => editCommitted)
.Where(x => x.Count > 0)
.Select(_ => Unit.Default);
}
public IObservable<Unit> ChangedDuringEdit
{
get { return this.changedDuringEdit; }
}
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.editActions.OnNext(EditAction.Begin);
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.editActions.OnNext(EditAction.Cancel);
this.Name = this.nameCopy;
}
public void EndEdit()
{
this.editActions.OnNext(EditAction.End);
}
private enum EditAction
{
Begin,
Cancel,
End
}
}
However, if several changes are cancelled, and then one is committed, the observable ticks several times on commit (once for each prior cancellation, and once again for the commit). Not to mention the fact that I get a List<Unit> which I don't actually need. In a way, this would still satisfy my use case, but not my curiosity or sense of code aesthetic.
I feel like Join should solve this fairly elegantly:
var nameChanged = this.ObservableForProperty(x => x.Name).Select(_ => Unit.Default);
var editBeginning = this.editActions.Where(x => x == EditAction.Begin);
var editCommitted = this.editActions.Where(x => x == EditAction.End);
var editCancelled = this.editActions.Where(x => x == EditAction.Cancel);
var editCancelledOrCommitted = editCancelled.Merge(editCommitted);
this.changedDuringEdit = editBeginning
.Join(nameChanged, _ => editCancelledOrCommitted, _ => editCancelledOrCommitted, (editAction, _) => editAction == EditAction.End)
.Where(x => x)
.Select(_ => Unit.Default);
But this doesn't work either. It seems Join is not subscribing to editCancelledOrCommitted, for reasons I don't understand.
Anyone have any ideas how to go about this cleanly?
Here's how I'd do it:
IObservable<Unit> beginEditSignal = ...;
IObservable<Unit> commitSignal = ...;
IObservable<Unit> cancelEditSignal = ...;
IObservable<T> propertyChanges = ...;
// this will yield an array after each commit
// that has all of the changes for that commit.
// nothing will be yielded if the commit is canceled
// or if the changes occur before BeginEdit.
IObservable<T[]> commitedChanges = beginEditSignal
.Take(1)
.SelectMany(_ => propertyChanges
.TakeUntil(commitSignal)
.ToArray()
.Where(changeList => changeList.Length > 0)
.TakeUntil(cancelEditSignal))
.Repeat();
// if you really only want a `Unit` when something happens
IObservable<Unit> changeCommittedSignal = beginEditSignal
.Take(1)
.SelectMany(_ => propertyChanges
.TakeUntil(commitSignal)
.Count()
.Where(c => c > 0)
.Select(c => Unit.Default)
.TakeUntil(cancelEditSignal))
.Repeat();
You have a timing problem that I don't think you have articulated yet; when are you hoping for the changes to tick?
either as they occur
once the commit happens
The clear and obvious problem with 1) is that you don't know if the changes will be committed, so why would you raise them. IMO, this only leaves option 2). If the change is cancelled, then no event is raised.
Next question I have is, do you want each change raised? ie. for the process
[Begin]-->[Name="fred"]-->[Name="bob"]-->[Commit]
Should this raise 1 or 2 events when the Commit is made? As you are only pushing the token type Unit, it seems redundant to push two values. This now leads me to think that you just want to push a Unit value when EndEdit() is executed and the values have changed.
This leaves us with a painfully simple implementation:
public class Person : ReactiveObject, IEditableObject
{
private readonly ISubject<Unit> changedDuringEdit = new Subject<Unit>();
private string name;
private string nameCopy;
public string Name
{
get { return this.name; }
set { this.RaiseAndSetIfChanged(ref this.name, value); }
}
public void BeginEdit()
{
this.nameCopy = this.name;
}
public void CancelEdit()
{
this.name = this.nameCopy;
}
public void EndEdit()
{
if(!string.Equals(this.nameCopy, this.name))
{
changedDuringEdit.OnNext(Unit.Default);
}
}
public IObservable<Unit> ChangedDuringEdit
{
get { return this.changedDuringEdit.AsObservable(); }
}
}
Is this what you are looking for? If not can you help me understand the complexities I am missing? If it is then I would be keen to flesh this out so that I wasn't recommending using Subjects :-)
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).