Given this setup,
public static class NotifyPropertyChangedExtensions
{
public static IObservable<EventPattern<PropertyChangedEventArgs>> WhenPropertyChanged(this INotifyPropertyChanged source)
{
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
ev => source.PropertyChanged += ev,
ev => source.PropertyChanged -= ev);
}
}
public class Node : INotifyPropertyChanged
{
public bool IsChecked
{
get => _isChecked;
set
{
if (_isChecked != value)
{
_isChecked = value;
OnPropertyChanged();
}
}
}
}
nodes.Values.Select(node => node.WhenPropertyChanged())
.Merge()
.Where(x => x.EventArgs.PropertyName == "IsChecked")
.Throttle(TimeSpan.FromMilliseconds(200))
.Subscribe(x =>
{
// do somethings
});
why does this not fire any events when I call?
nodeX.IsChecked = true;
immediately after setting up the subscription but works afterwards?
You do realize that is the intended behavior of the Throttle operator:
only emit an item from an Observable if a particular timespan has passed without it emitting another item
(source)
So in your case, since you perform the Merge before applying the Throttle operator, an item is emitted after a change to IsChecked and then after 200 ms.
Related
I am trying to set up an observable in a class that will tick each time an event fires on a member.
public class FooService
{
private BarProvider _barProvider;
public IObservable<BarChangedEventArgs> BarChanged { get; }
public FooService()
{
BarChanged =
Observable
.FromEventPattern<BarChangedHandler, BarChangedEventArgs>(
h => _barProvider.Changed += h,
h => _barProvider.Changed -= h)
.Select(p => p.EventArgs);
}
public void OccursSomeTimeAfterFooServiceCreation
(
Func<BarProvider> barProviderFactory
)
{
_barProvider = barProviderFactory();
}
}
What I think I need to do is set up the event handler observable after assigning the new value of _barProvider in the OccursLater method, as this is a new event source. However, I believe setting BarChanged at this later point, after consumers may have already subscribed, will break those existing subscriptions.
I would like consumers of the FooService to be able to subscribe to BarChanged at any point, and see the observable as one stream of event args, regardless of how many times OccursSomeTimeAfterFooServiceCreation is called after the subscription is created.
If your Observable - creation depends on stuff that can change e.g. your barProvider, you should always retrieve those from other Observables and then utilize the Switch() operator.
To achieve this I utilized the BehaviorSubject.
public class FooService
{
public BehaviorSubject<BarProvider> _barProviderSubject = new BehaviorSubject<BarProvider>(null); //or initialize this subject with the barprovider of your choice
public IObservable<BarChangedEventArgs> BarChanged { get; }
public FooService()
{
var barChangedChanged = _barProviderSubject.Where(bP => bP != null).Select(bP =>
Observable.FromEventPattern<BarChangedHandler, BarChangedEventArgs>(
h => bP.Changed += h,
h => bP.Changed -= h)
.Select(p => p.EventArgs)
);
BarChanged = barChangedChanged.Switch();
}
public void OccursSomeTimeAfterFooServiceCreation
(
Func<BarProvider> barProviderFactory
)
{
_barProviderSubject.OnNext(barProviderFactory());
}
}
The problem is that you don't observe classes or variables. You observe instances.
If I understand it correctly, you want your subscribers to be oblivious of the fact that the observed instance changes.
Try something like this:
public class FooService
{
private BarProvider _barProvider;
private Subject<BarChangedEventArgs> subject = new Subject<BarChangedEventArgs>();
public IObservable<BarChangedEventArgs> BarChanged { get; } = subject.AsObservable();
public FooService()
{
}
public void OccursSomeTimeAfterFooServiceCreation
(
Func<BarProvider> barProviderFactory
)
{
_barProvider = barProviderFactory();
BarChanged =
Observable
.FromEventPattern<BarChangedHandler, BarChangedEventArgs>(
h => _barProvider.Changed += h,
h => _barProvider.Changed -= h)
.Select(p => p.EventArgs)
.Subscribe(subject);
}
}
I am using ReactiveUI for a UWP app and have two commands CommandA and CommandB. CommandA when invoked attempts to make changes to the hardware. CommandB when invoked reads the hardware and provides the latest value.
I would like to invoke the CommandA (with the parameter as the CombBox value) when a ComboBox value is changed.
After the execution of CommandA I would like to invoke CommandB repeatedly till I get the value that is same as the one selected in ComboBox or if a timeout occurs. On timeout an error should be displayed.
To check that the CommandA is finished executing, I wrote the following code for [1]
this.WhenAnyValue(x => x.ComboBoxAValue)
.InvokeCommand(CommandA);
CommandA.IsExecuting
.Buffer(2,1)
.Where(t => t[0] == true && t[1] == false)
.Select( x=> Unit.Default)
.InvokeCommand(CommandB) // This statement would attempt to invoke CommandB only once
I am not sure how to do [2].
I'm conviced that exists a better solution, but here you have an approach:
public class MyCoolViewModel : ReactiveObject
{
private readonly Subject<Unit> _ticksToInvokeB;
private readonly ObservableAsPropertyHelper<bool> _commandAExecutedCorrectly;
public bool CommandAExecutedCorrectly => _commandAExecutedCorrectly.Value;
public ReactiveCommand<Unit, bool> CommandA { get; set; }
public ReactiveCommand<Unit, bool> CommandB { get; set; }
private string _comboBoxValue;
public string ComboBoxValue
{
get => _comboBoxValue;
set => this.RaiseAndSetIfChanged(ref _comboBoxValue, value);
}
public MyCoolViewModel()
{
//Subject implements IObservable<T> and IObserver<T>, also alow us to tick values to its observable
_ticksToInvokeB = new Subject<Unit>();
CommandA = ReactiveCommand.Create<Unit,bool>( _ =>
{
Console.WriteLine("Command A");
return true;
});
CommandB = ReactiveCommand.CreateFromTask<Unit,bool>( async _ =>
{
await Task.Delay(3000);
var isTheSame = DateTime.Now.Second % 2 == 0;
Console.WriteLine($"CommandB: is the same: {isTheSame}");
if(!isTheSame)//if the value is not the same, tick a new unit, since we ticked a new value, CommandA will be executed
_ticksToInvokeB.OnNext(Unit.Default);
return isTheSame;
});
CommandA//We just send commandA execution to an OAPH
.ToProperty(this, x => x.CommandAExecutedCorrectly, out _commandAExecutedCorrectly);
this.WhenAnyValue(x => x.ComboBoxValue)
.Skip(1) //this is because ComboBoxValue has a initial value (null) so we ignore it
.Select(_ => Unit.Default) //When it changes simply project an Unit
.InvokeCommand(CommandA);//Inke CommandA
this.WhenAnyValue(x => x.CommandAExecutedCorrectly)//When changes maded CommandA will set ChangesMaded to true
.Where(b => b) // only when Command A gets executed correctly
.Do(b => TickUnit()) // Tick a new unit
.Subscribe();
_ticksToInvokeB
.Throttle(TimeSpan.FromMilliseconds(200))//delay a little bit the next value
.InvokeCommand(CommandB);//invokes CommandB
}
private void TickUnit()
{
Console.WriteLine("Ticking new value");
_ticksToInvokeB.OnNext(Unit.Default);
}
}
Let me know if it helps you.
Regards.
First, instead of the buffer technique you used for detecting a completed command, I would do something like this. And instead of worrying about creating a command for CommandB, I would just execute it as a method. You can still use a command if you want, but I'll just be using an async call in this example. I'm using ExecuteUntilItYieldsTheSelectedComboBoxValue to continuously execute your CommandB logic in a loop until a matching value is found. It utilizes Observable.Create so you can control when OnNext is triggered. And you can tag on a Timeout to it and handle it in the Subscribe extension.
CommandA.IsExecuting
// IsExecuting has an initial value of false. We can skip that first value
.Skip(1)
// Filter until the executing state becomes false.
.Where(isExecuting => !isExecuting)
// Start an inner observable for your "CommandB" logic.
.Select(_ => ExecuteUntilItYieldsTheSelectedComboBoxValue())
// Whenever CommandA is invoked, dispose of the last inner observable subscription and resubscribe.
.Switch()
.Subscribe(
_ => Console.WriteLine("OnNext"),
ex => [display error message]);
...
private IObservable<Unit> ExecuteUntilItYieldsTheSelectedComboBoxValue()
{
return Observable
.Create<Unit>(
async o =>
{
int randNum = -1;
do
{
randNum = await GetRandomNumberAsync();
} while(randNum != ComboBoxValue);
o.OnNext(Unit.Default);
o.OnCompleted();
return Disposable.Empty;
})
.Timeout(TimeSpan.FromSeconds(3));
}
Update
Based on what Enigmativity pointed out, about needing to return something better than Disposable.Empty (to cancel any in-progress async task and break out of the loop), I'm changing the ExecuteUntilItYieldsTheSelectedComboBoxValue method to be the following:
private IObservable<Unit> ExecuteUntilItYieldsTheSelectedComboBoxValue()
{
return Observable
// Call our async method and pass in a cancellation token.
.FromAsync(ct => GetRandomNumberAsync(ct))
// Keep generating random numbers.
.Repeat()
// Until we find one that matches the ComboBoxValue.
.Where(x => x == ComboBoxValue)
// Just take the first one so this inner observable will complete.
.Take(1)
.Select(_ => Unit.Default)
.Timeout(TimeSpan.FromSeconds(3));
}
Note that it's still possible to make the Observable.Create method work correctly, but this edited solution is cleaner and less error prone. Let me know if you have any questions.
How can I re-write this code so that I don't have to chain Subscribers like below? Reason for asking is, this style will limit in an observable depending on another observable due to the style of the code, it can get confusing.
var results = myService
.GetData(accountId) // returns IObservable
.Subscribe(data =>
{
new MyWork().Execute(data) // returns IObservable
.Subscribe(result =>
{
myResults.Add(result);
WriteLine($"Result Id: {result.Id}");
WriteLine($"Result Status: {result.Pass}");
});
});
Added after 1st reply from Peter Bons
Below is the code for MyWork class that has the Execute Method
public class MyWork
{
public virtual IObservable<MyResult> Execute(MyData data)
{
MyResult result = null;
return IsMatch(data)
.Do(isMatch =>
{
if (isMatch)
{
result = new MyResult(1, true);
}
})
.Select(_ => result);
}
public IObservable<bool> IsMatch(MyData data)
{
return true;
}
}
It's really quite simple.
var results =
myService
.GetData(accountId)
.SelectMany(data => new MyWork().Execute(data))
.Subscribe(result =>
{
myResults.Add(result);
Console.WriteLine($"Result Id: {result.Id}");
Console.WriteLine($"Result Status: {result.Pass}");
});
If ever you are subscribing within a subscription then you are doing something wrong. Keep that in mind. There is almost always a way to make it a pure query with a single subscription.
Just to help out with testing, here's the code required to make this a Minimal, Complete, and Verifiable example.
public static class myService
{
public static IObservable<MyData> GetData(int x)
=> Observable.Return(new MyData());
}
public class MyWork
{
public virtual IObservable<MyResult> Execute(MyData data)
{
MyResult result = null;
return IsMatch(data)
.Do(isMatch =>
{
if (isMatch)
{
result = new MyResult() { Id = 1, Pass = true};
}
})
.Select(_ => result);
}
public IObservable<bool> IsMatch(MyData data)
{
return Observable.Return(true);
}
}
public class MyResult
{
public int Id;
public bool Pass;
}
public class MyData { }
I have a source stream and usually want to emit items as they arrive. But there is another observable - let's call it the "gate". When the gate is closed, the source items should buffer and be released only when the gate is opened.
I've been able to write a function to do this but it seems more complicated than it needs to be. I had to use the Observable.Create method. I assume there is a way to accomplish my goal using just a few lines of more functional code using the Delay or Buffer methods but I can't figure out how. Delay seems especially promising but I can't figure out how to sometimes delay and sometimes allow everything through immediately (a zero delay). Likewise I thought I could use Buffer followed by SelectMany; when the gate is open I'd have buffers of length 1 and when the gate is closed I'd have longer ones, but again I couldn't figure out how to make it work.
Here is what I've built that works with all my tests:
/// <summary>
/// Returns every item in <paramref name="source"/> in the order it was emitted, but starts
/// caching/buffering items when <paramref name="delay"/> emits true, and releases them when
/// <paramref name="delay"/> emits false.
/// </summary>
/// <param name="delay">
/// Functions as "gate" to start and stop the emitting of items. The gate is opened when true
/// and closed when false. The gate is open by default.
/// </param>
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay) =>
Observable.Create<T>(obs =>
{
ImmutableList<T> buffer = ImmutableList<T>.Empty;
bool isDelayed = false;
var conditionSubscription =
delay
.DistinctUntilChanged()
.Subscribe(i =>
{
isDelayed = i;
if (isDelayed == false)
{
foreach (var j in buffer)
{
obs.OnNext(j);
}
buffer = ImmutableList<T>.Empty;
}
});
var sourceSubscription =
source
.Subscribe(i =>
{
if (isDelayed)
{
buffer = buffer.Add(i);
}
else
{
obs.OnNext(i);
}
});
return new CompositeDisposable(sourceSubscription, conditionSubscription);
});
Here is another option that passes the tests. It is pretty concise but does not use the Delay or Buffer methods; I need to do the delaying/buffering manually.
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay) =>
delay
.StartWith(false)
.DistinctUntilChanged()
.CombineLatest(source, (d, i) => new { IsDelayed = d, Item = i })
.Scan(
seed: new { Items = ImmutableList<T>.Empty, IsDelayed = false },
accumulator: (sum, next) => new
{
Items = (next.IsDelayed != sum.IsDelayed) ?
(next.IsDelayed ? sum.Items.Clear() : sum.Items) :
(sum.IsDelayed ? sum.Items.Add(next.Item) : sum.Items.Clear().Add(next.Item)),
IsDelayed = next.IsDelayed
})
.Where(i => !i.IsDelayed)
.SelectMany(i => i.Items);
These are my tests:
[DataTestMethod]
[DataRow("3-a 6-b 9-c", "1-f", "3-a 6-b 9-c", DisplayName = "Start with explicit no_delay, emit all future items")]
[DataRow("3-a 6-b 9-c", "1-f 2-f", "3-a 6-b 9-c", DisplayName = "Start with explicit no_delay+no_delay, emit all future items")]
[DataRow("3-a 6-b 9-c", "1-t", "", DisplayName = "Start with explicit delay, emit nothing")]
[DataRow("3-a 6-b 9-c", "1-t 2-t", "", DisplayName = "Start with explicit delay+delay, emit nothing")]
[DataRow("3-a 6-b 9-c", "5-t 10-f", "3-a 10-b 10-c", DisplayName = "When delay is removed, all cached items are emitted in order")]
[DataRow("3-a 6-b 9-c 12-d", "5-t 10-f", "3-a 10-b 10-c 12-d", DisplayName = "When delay is removed, all cached items are emitted in order")]
public void DelayWhile(string source, string isDelayed, string expectedOutput)
{
(long time, string value) ParseEvent(string e)
{
var parts = e.Split('-');
long time = long.Parse(parts[0]);
string val = parts[1];
return (time, val);
}
IEnumerable<(long time, string value)> ParseEvents(string s) => s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(ParseEvent);
var scheduler = new TestScheduler();
var sourceEvents = ParseEvents(source).Select(i => OnNext(i.time, i.value)).ToArray();
var sourceStream = scheduler.CreateHotObservable(sourceEvents);
var isDelayedEvents = ParseEvents(isDelayed).Select(i => OnNext(i.time, i.value == "t")).ToArray();
var isDelayedStream = scheduler.CreateHotObservable(isDelayedEvents);
var expected = ParseEvents(expectedOutput).Select(i => OnNext(i.time, i.value)).ToArray();
var obs = scheduler.CreateObserver<string>();
var result = sourceStream.DelayWhile(isDelayedStream);
result.Subscribe(obs);
scheduler.AdvanceTo(long.MaxValue);
ReactiveAssert.AreElementsEqual(expected, obs.Messages);
}
[TestMethod]
public void DelayWhile_SubscribeToSourceObservablesOnlyOnce()
{
var scheduler = new TestScheduler();
var source = scheduler.CreateHotObservable<int>();
var delay = scheduler.CreateHotObservable<bool>();
// No subscriptions until subscribe
var result = source.DelayWhile(delay);
Assert.AreEqual(0, source.ActiveSubscriptions());
Assert.AreEqual(0, delay.ActiveSubscriptions());
// Subscribe once to each
var obs = scheduler.CreateObserver<int>();
var sub = result.Subscribe(obs);
Assert.AreEqual(1, source.ActiveSubscriptions());
Assert.AreEqual(1, delay.ActiveSubscriptions());
// Dispose subscriptions when subscription is disposed
sub.Dispose();
Assert.AreEqual(0, source.ActiveSubscriptions());
Assert.AreEqual(0, delay.ActiveSubscriptions());
}
[TestMethod]
public void DelayWhile_WhenSubscribeWithNoDelay_EmitCurrentValue()
{
var source = new BehaviorSubject<int>(1);
var emittedValues = new List<int>();
source.DelayWhile(Observable.Return(false)).Subscribe(i => emittedValues.Add(i));
Assert.AreEqual(1, emittedValues.Single());
}
// Subscription timing issue?
[TestMethod]
public void DelayWhile_WhenSubscribeWithDelay_EmitNothing()
{
var source = new BehaviorSubject<int>(1);
var emittedValues = new List<int>();
source.DelayWhile(Observable.Return(true)).Subscribe(i => emittedValues.Add(i));
Assert.AreEqual(0, emittedValues.Count);
}
[TestMethod]
public void DelayWhile_CoreScenario()
{
var source = new BehaviorSubject<int>(1);
var delay = new BehaviorSubject<bool>(false);
var emittedValues = new List<int>();
// Since no delay when subscribing, emit value
source.DelayWhile(delay).Subscribe(i => emittedValues.Add(i));
Assert.AreEqual(1, emittedValues.Single());
// Turn on delay and buffer up a few; nothing emitted
delay.OnNext(true);
source.OnNext(2);
source.OnNext(3);
Assert.AreEqual(1, emittedValues.Single());
// Turn off delay; should release the buffered items
delay.OnNext(false);
Assert.IsTrue(emittedValues.SequenceEqual(new int[] { 1, 2, 3 }));
}
EDIT: I forgot about the problems you'll run into with Join and Join-based operators (like WithLatestFrom) when having two cold observables. Needless to say, that criticism mentioned below about lack of transactions is more apparent than ever.
I would recommend this, which is more like my original solution but using the Delay overload. It passes all tests except DelayWhile_WhenSubscribeWithDelay_EmitNothing. To get around that, I would create an overload that would accept a starting default value:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay, bool isGateClosedToStart)
{
return source.Publish(_source => delay
.DistinctUntilChanged()
.StartWith(isGateClosedToStart)
.Publish(_delay => _delay
.Select(isGateClosed => isGateClosed
? _source.TakeUntil(_delay).Delay(_ => _delay)
: _source.TakeUntil(_delay)
)
.Merge()
)
);
}
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return DelayWhile(source, delay, false);
}
Old answer:
I read a book recently criticizing Rx for not supporting transactions, and my first try at solving this would be a great example why:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return source.Publish(_source => delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => _delay
.Select(isGateClosed => isGateClosed
? _source.Buffer(_delay).SelectMany(l => l)
: _source)
.Switch()
)
);
}
That should work, except there's too many things relying on the delay observable, and the subscription order matters: In this case the Switch switches before the Buffer ends, so nothing ends up coming out when the delay gate is closed.
This can be fixed as follows:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return source.Publish(_source => delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => _delay
.Select(isGateClosed => isGateClosed
? _source.TakeUntil(_delay).Buffer(_delay).SelectMany(l => l)
: _source.TakeUntil(_delay)
)
.Merge()
)
);
}
My next try passed all your tests, and uses your desired Observable.Delay overload as well:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => source
.Join(_delay,
s => Observable.Empty<Unit>(),
d => _delay,
(item, isGateClosed) => isGateClosed
? Observable.Return(item).Delay(, _ => _delay)
: Observable.Return(item)
)
.Merge()
);
}
The Join could be reduced to a WithLatestFrom like this:
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
return delay
.DistinctUntilChanged()
.StartWith(false)
.Publish(_delay => source
.WithLatestFrom(_delay,
(item, isGateClosed) => isGateClosed
? Observable.Return(item).Delay(_ => _delay)
: Observable.Return(item)
)
.Merge()
);
}
A proposed concise answer. It looks like it should work but it doesn't pass all the tests.
public static IObservable<T> DelayWhile<T>(this IObservable<T> source, IObservable<bool> delay)
{
source = source.Publish().RefCount();
delay = delay.Publish().RefCount();
var delayRemoved = delay.Where(i => i == false);
var sourceWhenNoDelay = source.WithLatestFrom(delay.StartWith(false), (s, d) => d).Where(i => !i);
return
source
.Buffer(bufferClosingSelector: () => delayRemoved.Merge(sourceWhenNoDelay))
.SelectMany(i => i);
}
I'll create an observable (through a variety of means) and return it to interested parties, but when they're done listening, I'd like to tear down the observable so it doesn't continue consuming resources. Another way to think of it as creating topics in a pub sub system. When no one is subscribed to a topic any more, you don't want to hold the topic and its filtering around anymore.
Rx already has an operator to suit your needs - well two actually - Publish & RefCount.
Here's how to use them:
IObservable xs = ...
var rxs = xs.Publish().RefCount();
var sub1 = rxs.Subscribe(x => { });
var sub2 = rxs.Subscribe(x => { });
//later
sub1.Dispose();
//later
sub2.Dispose();
//The underlying subscription to `xs` is now disposed of.
Simple.
If I have understood your question you want to create the observable such that when all subscribers have disposed their subscription i.e there is no more subscriber, then you want to execute a clean up function which will stop the observable from production further values.
If this is what you want then you can do something like below:
//Wrap a disposable
public class WrapDisposable : IDisposable
{
IDisposable disp;
Action act;
public WrapDisposable(IDisposable _disp, Action _act)
{
disp = _disp;
act = _act;
}
void IDisposable.Dispose()
{
act();
disp.Dispose();
}
}
//Observable that we want to clean up after all subs are done
public static IObservable<long> GenerateObs(out Action cleanup)
{
cleanup = () =>
{
Console.WriteLine("All subscribers are done. Do clean up");
};
return Observable.Interval(TimeSpan.FromSeconds(1));
}
//Wrap the observable
public static IObservable<T> WrapToClean<T>(IObservable<T> obs, Action onAllDone)
{
int count = 0;
return Observable.CreateWithDisposable<T>(ob =>
{
var disp = obs.Subscribe(ob);
Interlocked.Increment(ref count);
return new WrapDisposable(disp,() =>
{
if (Interlocked.Decrement(ref count) == 0)
{
onAllDone();
}
});
});
}
//Usage example:
Action cleanup;
var obs = GenerateObs(out cleanup);
var newObs = WrapToClean(obs, cleanup);
newObs.Take(6).Subscribe(Console.WriteLine);
newObs.Take(5).Subscribe(Console.WriteLine);