public override void OnNext(TLeft value) { var s = new Subject <TRight>(); var id = 0; var rightID = 0; lock (_parent._gate) { id = _parent._leftID++; rightID = _parent._rightID; _parent._leftMap.Add(id, s); } var window = new WindowObservable <TRight>(s, _parent._refCount); // BREAKING CHANGE v2 > v1.x - Order of evaluation or the _leftDurationSelector and _resultSelector now consistent with Join. var duration = default(IObservable <TLeftDuration>); try { duration = _parent._leftDurationSelector(value); } catch (Exception exception) { OnError(exception); return; } var durationObserver = new DurationObserver(this, id, s); _parent._group.Add(durationObserver); // BREAKING CHANGE v2 > v1.x - The duration sequence is subscribed to before the result sequence is evaluated. durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); TResult result; try { result = _parent._resultSelector(value, window); } catch (Exception exception) { OnError(exception); return; } lock (_parent._gate) { _parent.ForwardOnNext(result); foreach (var rightValue in _parent._rightMap) { if (rightValue.Key < rightID) { s.OnNext(rightValue.Value); } } } }
public override void OnNext(TLeft value) { var id = 0; var rightID = 0; lock (_parent._gate) { id = _parent._leftID++; rightID = _parent._rightID; _parent._leftMap.Add(id, value); } var duration = default(IObservable <TLeftDuration>); try { duration = _parent._leftDurationSelector(value); } catch (Exception exception) { _parent.ForwardOnError(exception); return; } var durationObserver = new DurationObserver(this, id); _parent._group.Add(durationObserver); durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); lock (_parent._gate) { foreach (var rightValue in _parent._rightMap) { if (rightValue.Key < rightID) { var result = default(TResult); try { result = _parent._resultSelector(value, rightValue.Value); } catch (Exception exception) { _parent.ForwardOnError(exception); return; } _parent.ForwardOnNext(result); } } } }
public override void OnNext(TRight value) { int id; int leftID; lock (_parent._gate) { id = _parent._rightID++; leftID = _parent._leftID; _parent._rightMap.Add(id, value); } IObservable <TRightDuration> duration; try { duration = _parent._rightDurationSelector(value); } catch (Exception exception) { _parent.ForwardOnError(exception); return; } var durationObserver = new DurationObserver(this, id); _parent._group.Add(durationObserver); durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); lock (_parent._gate) { foreach (var leftValue in _parent._leftMap) { if (leftValue.Key < leftID) { TResult result; try { result = _parent._resultSelector(leftValue.Value, value); } catch (Exception exception) { _parent.ForwardOnError(exception); return; } _parent.ForwardOnNext(result); } } } }
public override void OnNext(TRight value) { var id = 0; var leftID = 0; lock (_parent._gate) { id = _parent._rightID++; leftID = _parent._leftID; _parent._rightMap.Add(id, value); } var duration = default(IObservable <TRightDuration>); try { duration = _parent._rightDurationSelector(value); } catch (Exception exception) { OnError(exception); return; } var durationObserver = new DurationObserver(this, id); _parent._group.Add(durationObserver); durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); lock (_parent._gate) { foreach (var o in _parent._leftMap) { if (o.Key < leftID) { o.Value.OnNext(value); } } } }
public override void OnNext(TSource value) { TKey key; try { key = _keySelector(value); } catch (Exception exception) { Error(exception); return; } var fireNewMapEntry = false; ISubject <TElement> writer; try { // // Note: The box instruction in the IL will be erased by the JIT in case T is // a value type. In fact, the whole if block will go away and we'll end // up with nothing but the GetOrAdd call below. // // See GroupBy for more information and confirmation of this fact using // the SOS debugger extension. // if (key == null) { lock (_nullGate) { if (_null == null) { _null = NewSubject(); fireNewMapEntry = true; } writer = _null; } } else { writer = _map.GetOrAdd(key, NewSubject, out fireNewMapEntry); } } catch (Exception exception) { Error(exception); return; } if (fireNewMapEntry) { var group = new GroupedObservable <TKey, TElement>(key, writer, _refCountDisposable); var durationGroup = new GroupedObservable <TKey, TElement>(key, writer); IObservable <TDuration> duration; try { duration = _durationSelector(durationGroup); } catch (Exception exception) { Error(exception); return; } lock (_gate) { ForwardOnNext(group); } var durationObserver = new DurationObserver(this, key, writer); _groupDisposable.Add(durationObserver); durationObserver.SetResource(duration.SubscribeSafe(durationObserver)); } TElement element; try { element = _elementSelector(value); } catch (Exception exception) { Error(exception); return; } // // ISSUE: Rx v1.x shipped without proper handling of the case where the duration // sequence fires concurrently with the OnNext code path here. In such a // case, the subject can be completed before we get a chance to send out // a new element. However, a resurrected group for the same key won't get // to see the element either. To guard against this case, we'd have to // check whether the OnNext call below lost the race, and resurrect a new // group if needed. Unfortunately, this complicates matters when the // duration selector triggers synchronously (e.g. Return or Empty), which // causes the group to terminate immediately. We should not get stuck in // this case, repeatedly trying to resurrect a group that always ends // before we can send the element into it. Also, users may expect this // base case to mean no elements will ever be produced, so sending the // element into the group before starting the duration sequence may not // be a good idea either. For the time being, we'll leave this as-is and // revisit the behavior for vNext. Nonetheless, we'll add synchronization // to ensure no concurrent calls to the subject are made. // writer.OnNext(element); }