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 :-)
Related
I am trying to create a simple class. ColumnSort member is a list of items in comma delimited text "Car,Book,Food".
ColumnSortList creates a List
Car
Book
Food
C# and SonarQube is mentioning items like Error
Get: Add a way to break out of this property accessor's recursion.
Set: Use the 'value' parameter in this property set accessor declaration
How would I resolve these to make warnings/ errors (in SonarQube) go away? Open to making code more efficient also.
Note: columnSortList is purely supposed to be a read only computed field from ColumnSort string.
public class PageModel
{
public int Page { get; set; }
public int Limit { get; set; }
public string ColumnSort { get; set; }
public IEnumerable<string> columnSortList
{
get
{
return columnSortList;
}
set
{
if (ColumnSort == null)
{
columnSortList = null;
}
else
{
columnSortList = ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
If columnSortList is intended to be purely read-only, computed from ColumnSort, then you should not have a set method at all. All the logic should go inside get like this:
public IEnumerable<string> columnSortList
{
get
{
if (ColumnSort == null)
{
return Enumerable.Empty<string>();
}
else
{
return ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
Your getter is returning itself, which you can't do, and your setter is setting itself, which you also can't do. This here seems to be what you want:
public IEnumerable<string> columnSortList
{
get
{
if (ColumSort == null)
{
return new List<string>();
}
else
{
return ColumnSort.Split(',')
.Select(x => x.Trim())
.Where(x => !string.IsNullOrWhiteSpace(x))
.AsEnumerable();
}
}
}
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
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
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).