示例#1
0
 /// <summary>
 /// Initializes a new instance of the <see cref="GroupController"/> class.
 /// </summary>
 public GroupController()
 {
     _cleanUp = Disposable.Create(() => _regroupSubject.OnCompleted());
 }
示例#2
0
        public void UsingAsync_CancelFactory()
        {
            var N = 10;// 0000;

            for (var i = 0; i < N; i++)
            {
                var gate     = new object();
                var disposed = false;
                var called   = false;

                var s = new ManualResetEvent(false);
                var e = new ManualResetEvent(false);
                var x = new ManualResetEvent(false);

                var xs = Observable.Using(
                    ct => Task.Factory.StartNew(() =>
                                                Disposable.Create(() =>
                {
                    lock (gate)
                    {
                        disposed = true;
                    }
                })
                                                ),
                    (_, ct) => Task.Factory.StartNew(() =>
                {
                    s.Set();
                    e.WaitOne();
                    while (!ct.IsCancellationRequested)
                    {
                        ;
                    }

                    x.Set();
                    return(Observable.Defer(() =>
                    {
                        called = true;
                        return Observable.Return(42);
                    }));
                })
                    );

                var d = xs.Subscribe(_ => { });

                s.WaitOne();

                //
                // This will *eventually* set the CancellationToken. There's a fundamental race between observing the CancellationToken
                // and returning the IDisposable that will set the CancellationTokenSource. Notice this is reflected in the code above,
                // by looping until the CancellationToken is set.
                //
                d.Dispose();

                e.Set();
                x.WaitOne();

                while (true)
                {
                    lock (gate)
                    {
                        if (disposed)
                        {
                            break;
                        }
                    }
                }

                Assert.False(called, i.ToString());
            }
        }
 public static IDisposable RegisterHover(this View view, EventHandler <View.HoverEventArgs> handler)
 {
     view.Hover += handler;
     return(Disposable.Create(() => view.RunIfNativeInstanceAvailable(v => v.Hover -= handler)));
 }
示例#4
0
 public IDisposable Subscribe(IObserver <TProduct> observer)
 {
     ConsumerObserver = observer;
     return(Disposable.Create(() => ConsumerObserver = null));
 }
示例#5
0
        public IObservable <IGroupChangeSet <TObject, TKey, TGroupKey> > Run()
        {
            return(Observable.Create <IGroupChangeSet <TObject, TKey, TGroupKey> >
                   (
                       observer =>
            {
                var locker = new object();

                //create source group cache
                var sourceGroups = _source.Synchronize(locker)
                                   .Group(_groupSelector)
                                   .DisposeMany()
                                   .AsObservableCache();

                //create parent groups
                var parentGroups = _resultGroupSource.Synchronize(locker)
                                   .Transform(x =>
                {
                    //if child already has data, populate it.
                    var result = new ManagedGroup <TObject, TKey, TGroupKey>(x);
                    var child = sourceGroups.Lookup(x);
                    if (child.HasValue)
                    {
                        //dodgy cast but fine as a groups is always a ManagedGroup;
                        var group = (ManagedGroup <TObject, TKey, TGroupKey>)child.Value;
                        result.Update(updater => updater.Update(group.GetInitialUpdates()));
                    }
                    return result;
                })
                                   .DisposeMany()
                                   .AsObservableCache();

                //connect to each individual item and update the resulting group
                var updateFromcChilds = sourceGroups.Connect()
                                        .SubscribeMany(x => x.Cache.Connect().Subscribe(updates =>
                {
                    var groupToUpdate = parentGroups.Lookup(x.Key);
                    if (groupToUpdate.HasValue)
                    {
                        groupToUpdate.Value.Update(updater => updater.Update(updates));
                    }
                }))
                                        .DisposeMany()
                                        .Subscribe();

                var notifier = parentGroups
                               .Connect()
                               .Select(x =>
                {
                    var groups = x.Select(s => new Change <IGroup <TObject, TKey, TGroupKey>, TGroupKey>(s.Reason, s.Key, s.Current));
                    return new GroupChangeSet <TObject, TKey, TGroupKey>(groups);
                })
                               .SubscribeSafe(observer);

                return Disposable.Create(() =>
                {
                    notifier.Dispose();
                    sourceGroups.Dispose();
                    parentGroups.Dispose();
                    updateFromcChilds.Dispose();
                });
            }));
        }
 public static IDisposable RegisterLayoutChange(this View view, EventHandler <View.LayoutChangeEventArgs> handler)
 {
     view.LayoutChange += handler;
     return(Disposable.Create(() => view.RunIfNativeInstanceAvailable(v => v.LayoutChange -= handler)));
 }
示例#7
0
        private void ProcessSubscription(string subscriptionId,
                                         IMessage message,
                                         RequestStreamHandler <TRequest, TUpdate> requestStreamHandler)
        {
            // In order to prevent races/resource-leaks here we must:
            //  * Monitor session diconnections before attempting to create a request context.
            //      - Otherwise, a session could be destroyed and we would never clean up its resources, as we'd
            //        miss the notification.
            //  * Create the subscription to monitor sessions and add update our state all within a single
            //    critical region. In addition to this, we must ensure session destruction callbacks are
            //    fired on a different thread.
            //      - Otherwise, we may attempt to remove the session's resources before they are added.
            var sessionId        = message.SessionId;
            var replyDestination = message.ReplyTo;
            var subscription     = new CompositeDisposable();

            TRequest request;

            if (!TryDeserializeRequest(subscriptionId, message, out request))
            {
                return;
            }

            var clientId = message.Properties.GetString(OperationKeys.ClientId);

            if (clientId == null)
            {
                Log.Warning("No ClientId found. Ignoring.");
                return;
            }

            var sessionDestroyedHandler = new AnonymousUserSessionHandler(_ => { },
                                                                          _ =>
            {
                lock (_subscriptions)
                {
                    // TODO: Make the Java version clean all resources hooked up here.
                    // ReSharper disable once AccessToDisposedClosure
                    subscription.Dispose();
                    _subscriptions.Remove(subscriptionId);
                }
            });

            lock (_subscriptions)
            {
                // TODO: How do we clean up when IsSessionRequired == false?
                if (IsSessionRequired)
                {
                    subscription.Add(UserSessionCache.Subscribe(sessionId, sessionDestroyedHandler));
                }

                var context = CreateRequestContext(message);
                if (context == null)
                {
                    Log.Warning("Failed to create request context. Ignoring.");
                    subscription.Dispose(); // Don't listen for session destruction if it doesn't exist.
                    return;
                }

                // At this point we know the session exists or existed and we know it will be cleared up (after we
                // exit the critical region) by the sessionResourceCleaner if it is destroyed.
                _subscriptions.Add(subscriptionId, subscription);

                try
                {
                    const int notFinished              = 0;
                    const int finished                 = 1;
                    var       subscriptionState        = notFinished;
                    var       notificationSubscription = requestStreamHandler(context,
                                                                              request,
                                                                              new AnonymousStreamHandler <TUpdate>(
                                                                                  // TODO: I remove the session from the lookup AND ALSO dipose subscription here.
                                                                                  //       This is analagous to the AutoDetachObserver<T> in Rx. Should we do the same in the Java version?
                                                                                  //       Review with John. -ZB
                                                                                  update => OnUpdated(subscriptionId, replyDestination, update),
                                                                                  error =>
                    {
                        if (Interlocked.Exchange(ref subscriptionState, finished) ==
                            notFinished)
                        {
                            OnFailed(subscriptionId, replyDestination, error);
                        }
                    },
                                                                                  () =>
                    {
                        if (Interlocked.Exchange(ref subscriptionState, finished) ==
                            notFinished)
                        {
                            OnCompleted(subscriptionId, replyDestination);
                        }
                    }));
                    subscription.Add(Disposable.Create(() =>
                    {
                        var hasAlreadyFinished = Interlocked.Exchange(ref subscriptionState, finished) == finished;
                        if (!hasAlreadyFinished)
                        {
                            notificationSubscription.Dispose();
                        }
                    }));
                }
                catch (Exception e)
                {
                    const string error = "Failed to process request";
                    OnFailed(subscriptionId, replyDestination, new MessagingException(error, e));
                    Log.Error(error, e);
                }
            }

            SendAck(subscriptionId, replyDestination, clientId);
        }
示例#8
0
        public LogEntryViewer(ILogEntryService logEntryService)
        {
            //build an observable filter
            var filter = this.WhenAnyValue(x => x.SearchText)
                         .Throttle(TimeSpan.FromMilliseconds(250))
                         .Select(BuildFilter);

            //filter, sort and populate reactive list.
            var loader = logEntryService.Items.Connect()
                         .Transform(le => new LogEntryProxy(le))
                         .DelayRemove(TimeSpan.FromSeconds(0.75), proxy => proxy.FlagForRemove())
                         .Filter(filter)
                         .Sort(SortExpressionComparer <LogEntryProxy> .Descending(le => le.TimeStamp).ThenByDescending(l => l.Key), SortOptions.UseBinarySearch)
                         .ObserveOn(RxApp.MainThreadScheduler)
                         .Bind(Data)
                         .DisposeMany()
                         .Subscribe();

            //aggregate total items
            var summariser = logEntryService.Items.Connect()
                             .QueryWhenChanged(items =>
            {
                var debug = items.Count(le => le.Level == LogLevel.Debug);
                var info  = items.Count(le => le.Level == LogLevel.Info);
                var warn  = items.Count(le => le.Level == LogLevel.Warning);
                var error = items.Count(le => le.Level == LogLevel.Error);
                return(new LogEntrySummary(debug, info, warn, error));
            })
                             .Subscribe(s => Summary = s);

            //manage user selection, delete items command
            var selectedItems = _selectionController.SelectedItems.Connect().Publish();

            //Build a message from selected items
            _deleteItemsText = selectedItems.QueryWhenChanged(query =>
            {
                if (query.Count == 0)
                {
                    return("Select log entries to delete");
                }
                if (query.Count == 1)
                {
                    return("Delete selected log entry?");
                }
                return($"Delete {query.Count} log entries?");
            })
                               .ToProperty(this, viewmodel => viewmodel.DeleteItemsText, "Select log entries to delete");


            //make a command out of selected items - enabling the command when there is a selection
            DeleteCommand = ReactiveCommand.Create(() =>
            {
                var toRemove = _selectionController.SelectedItems.Items.Select(proxy => proxy.Original).ToArray();
                _selectionController.Clear();
                logEntryService.Remove(toRemove);
            }, selectedItems.QueryWhenChanged(query => query.Count > 0));

            var connected = selectedItems.Connect();

            _cleanUp = Disposable.Create(() =>
            {
                loader.Dispose();
                connected.Dispose();
                _deleteItemsText.Dispose();
                DeleteCommand.Dispose();
                _selectionController.Dispose();
                summariser.Dispose();
            });
        }
示例#9
0
        private IConnectableObservable <byte[]> CreateObserver(WebSocket webSocket)
        {
            return(Observable.Create <byte[]>(async(observer, cancellation) =>
            {
                try
                {
                    while (webSocket.IsConnected && !cancellation.IsCancellationRequested)
                    {
                        WebSocketMessageReadStream message =
                            await webSocket.ReadMessageAsync(cancellation)
                            .ConfigureAwait(false);
                        if (message == null)
                        {
                            continue;
                        }

                        switch (message.MessageType)
                        {
                        case WebSocketMessageType.Text:
                            string messageContent;
                            using (StreamReader streamReader =
                                       new StreamReader(message, Encoding.UTF8))
                            {
                                messageContent = await streamReader.ReadToEndAsync();
                            }

                            WebCastMessage webCastMessage =
                                messageContent.FromJson <WebCastMessage>();
                            switch (webCastMessage.Type)
                            {
                            case "metadata":
                                _logger.Debug("Metadata received: {webCastMessage.Data}");
                                break;

                            default:
                                _logger.Warning("Invalid message");
                                break;
                            }

                            break;

                        case WebSocketMessageType.Binary:
                            byte[] bytes = message.ReadFully();
                            observer.OnNext(bytes);

                            break;
                        }
                    }

                    observer.OnCompleted();
                }
                catch (Exception exception)
                {
                    _logger.Error(exception, "Error Handling connection");
                    observer.OnError(exception);
                }

                return Disposable.Create(webSocket.Dispose);
            })
                   .Publish());
        }
        public static async Task <IDisposable> UseWaitAsync(this SemaphoreSlim semaphore)
        {
            await semaphore.WaitAsync();

            return(Disposable.Create(() => semaphore.Release()));
        }
示例#11
0
 /* ----------------------------------------------------------------- */
 ///
 /// SubscribeAsync
 ///
 /// <summary>
 /// データ受信時に非同期で実行する処理を登録します。
 /// </summary>
 ///
 /* ----------------------------------------------------------------- */
 public IDisposable SubscribeAsync(Func <TimeSpan, Task> action)
 {
     Subscriptions.Add(action);
     return(Disposable.Create(() => Subscriptions.Remove(action)));
 }
示例#12
0
        private static void Impl(bool primaryFirst, bool primaryRandom, IEnumerable <int> nDependents)
        {
            var rand = new Random();

            foreach (var n in nDependents)
            {
                var e            = new ManualResetEvent(false);
                var hasDependent = new ManualResetEvent(false);
                var r            = new RefCountDisposable(Disposable.Create(() => { e.Set(); }));

                var d = default(IDisposable);
                if (primaryFirst)
                {
                    d = r.GetDisposable();
                    r.Dispose();
                }
                else if (primaryRandom)
                {
                    var sleep = rand.Next(0, 10) == 0 /* 10% chance */ ? rand.Next(2, 100) : 0;

                    ThreadPool.QueueUserWorkItem(_ =>
                    {
                        hasDependent.WaitOne();
                        Helpers.SleepOrSpin(sleep);
                        r.Dispose();
                    });

                    if (n == 0)
                    {
                        hasDependent.Set();
                    }
                }

                Console.Write(n + " - ");

                var cd = new CountdownEvent(n * 2);
                for (int i = 0; i < n; i++)
                {
                    var j = i;

                    var sleep1 = rand.Next(0, 10) == 0 /* 10% chance */ ? rand.Next(2, 100) : 0;
                    var sleep2 = rand.Next(0, 10) == 0 /* 10% chance */ ? rand.Next(2, 100) : 0;
                    var sleep3 = rand.Next(0, 10) == 0 /* 10% chance */ ? rand.Next(2, 100) : 0;

                    ThreadPool.QueueUserWorkItem(_ =>
                    {
                        Helpers.SleepOrSpin(sleep1);

                        Console.Write("+");

                        var f = r.GetDisposable();

                        if (j == 0)
                        {
                            hasDependent.Set();
                        }

                        Helpers.SleepOrSpin(sleep2);

                        ThreadPool.QueueUserWorkItem(__ =>
                        {
                            Helpers.SleepOrSpin(sleep3);

                            f.Dispose();

                            Console.Write("-");

                            cd.Signal();
                        });

                        cd.Signal();
                    });
                }

                cd.Wait();

                if (primaryFirst)
                {
                    d.Dispose();
                }
                else if (!primaryRandom)
                {
                    r.Dispose();
                }

                e.WaitOne();

                Console.WriteLine(".");
            }
        }
示例#13
0
 public IDisposable StartPeriodicTimer(Action action, TimeSpan period)
 {
     _action = action;
     _period = period;
     return(Disposable.Create(() => _action = null));
 }
示例#14
0
        // For requests that do not use requestId.
        // Single result(end=null): CurrentTime, ScannerParameters
        // Multiple results (end=false=>true): AccountPositions, OpenOrders
        // Continuous results (end=false): AccountUpdate, NewsBulletins
        // End type: not type T
        internal static IObservable <T> ToObservable <T>(
            this IObservable <object> source, Action subscribe, Action unsubscribe = null, Func <object, bool> end = null)
            where T : class
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (subscribe == null)
            {
                throw new ArgumentNullException(nameof(subscribe));
            }

            return(Observable.Create <T>(observer =>
            {
                bool?cancelable = null;

                var subscription = source
                                   .Finally(() => cancelable = false)
                                   .Subscribe(
                    onNext: m =>
                {
                    var theEnd = end != null && end(m);
                    if (m is T t && (end == null || !theEnd))
                    {
                        observer.OnNext(t);
                    }
                    if (end == null || theEnd)
                    {
                        cancelable = false;
                        observer.OnCompleted();
                    }
                },
                    onError: observer.OnError,
                    onCompleted: observer.OnCompleted);

                if (cancelable == null)
                {
                    subscribe();
                }
                if (cancelable == null)
                {
                    cancelable = true;
                }

                return Disposable.Create(() =>
                {
                    if (cancelable == true)
                    {
                        try
                        {
                            unsubscribe?.Invoke();
                        }
                        catch (Exception e)
                        {   // ignored
                            Debug.WriteLine("Unexpected: " + e.ToString());
                        }
                    }
                    subscription.Dispose();
                });
            }));
示例#15
0
 private IDisposable StartObserving(Task task)
 {
     task.ContinueWith(ContinueObservedTask, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
     return(Disposable.Create(() => UnobserveTaskFailure(task)));
 }
 /// <summary>
 /// When this method is called, an object will not fire change
 /// notifications (neither traditional nor Observable notifications)
 /// until the return value is disposed.
 /// </summary>
 /// <returns>An object that, when disposed, reenables change
 /// notifications.</returns>
 public IDisposable SuppressChangeNotifications()
 {
     Interlocked.Increment(ref changeNotificationsSuppressed);
     return(Disposable.Create(() => Interlocked.Decrement(ref changeNotificationsSuppressed)));
 }
        /// <summary>
        /// Create an observable for the specified window or HwndSource
        /// </summary>
        /// <param name="window">Window</param>
        /// <param name="hWndSource">HwndSource</param>
        /// <returns>IObservable</returns>
        private static IObservable <WindowMessageInfo> WinProcMessages(Window window, HwndSource hWndSource)
        {
            if (window == null && hWndSource == null)
            {
                throw new NotSupportedException("One of Window or HwndSource must be supplied");
            }
            if (window != null && hWndSource != null)
            {
                throw new NotSupportedException("Either Window or HwndSource must be supplied");
            }

            return(Observable.Create <WindowMessageInfo>(observer =>
            {
                // This handles the message, and generates the observable OnNext
                IntPtr WindowMessageHandler(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
                {
                    observer.OnNext(WindowMessageInfo.Create(hwnd, msg, wParam, lParam));
                    // ReSharper disable once AccessToDisposedClosure
                    if (hWndSource.IsDisposed)
                    {
                        observer.OnCompleted();
                    }
                    return IntPtr.Zero;
                }

                void HwndSourceDisposedHandle(object sender, EventArgs e)
                {
                    observer.OnCompleted();
                }

                void RegisterHwndSource()
                {
                    hWndSource.Disposed += HwndSourceDisposedHandle;
                    hWndSource.AddHook(WindowMessageHandler);
                }

                if (window != null)
                {
                    hWndSource = window.ToHwndSource();
                }
                if (hWndSource != null)
                {
                    RegisterHwndSource();
                }
                else
                {
                    // No, try to get it later
                    window.SourceInitialized += (sender, args) =>
                    {
                        hWndSource = window.ToHwndSource();
                        RegisterHwndSource();
                        // Simulate the WM_NCCREATE
                        observer.OnNext(WindowMessageInfo.Create(hWndSource.Handle, (int)WindowsMessages.WM_NCCREATE, IntPtr.Zero, IntPtr.Zero));
                    };
                }

                return Disposable.Create(() =>
                {
                    hWndSource.Disposed -= HwndSourceDisposedHandle;
                    hWndSource.RemoveHook(WindowMessageHandler);
                    hWndSource.Dispose();
                });
            })
                   // Make sure there is always a value produced when connecting
                   .Publish()
                   .RefCount());
        }
示例#18
0
 public static IDisposable RegisterContextMenuCreated(this View view, EventHandler <View.CreateContextMenuEventArgs> handler)
 {
     view.ContextMenuCreated += handler;
     return(Disposable.Create(() => view.RunIfNativeInstanceAvailable(v => v.ContextMenuCreated -= handler)));
 }
示例#19
0
 public IDisposable Subscribe(IObserver <int> observer)
 {
     _subscribeCount++;
     _observer = observer;
     return(Disposable.Create(() => { _disposed = true; }));
 }
 public static IDisposable RegisterLongClick(this View view, EventHandler <View.LongClickEventArgs> handler)
 {
     view.LongClick += handler;
     return(Disposable.Create(() => view.RunIfNativeInstanceAvailable(v => v.LongClick -= handler)));
 }
 /* ----------------------------------------------------------------- */
 ///
 /// Subscribe
 ///
 /// <summary>
 /// SendCallback で実行される処理を登録します。
 /// </summary>
 ///
 /// <param name="action">処理を表すオブジェクト</param>
 ///
 /// <returns>登録解除用オブジェクト</returns>
 ///
 /* ----------------------------------------------------------------- */
 public IDisposable Subscribe(Action <T> action)
 {
     _subscriptions.Add(action);
     return(Disposable.Create(() => _subscriptions.Remove(action)));
 }
 public static IDisposable RegisterKeyPress(this View view, EventHandler <View.KeyEventArgs> handler)
 {
     view.KeyPress += handler;
     return(Disposable.Create(() => view.RunIfNativeInstanceAvailable(v => v.KeyPress -= handler)));
 }
示例#23
0
        protected override void OnEnable()
        {
            base.OnEnable();

            xpcfComponentManager = xpcf_api.getComponentManagerInstance();
            Disposable.Create(xpcfComponentManager.clear).AddTo(subscriptions);
            xpcfComponentManager.AddTo(subscriptions);

#if !UN
            conf.path = File.ReadAllText("confPath.txt");
#endif
            if (xpcfComponentManager.load(conf.path) != XPCFErrorCode._SUCCESS)
            {
                Debug.LogErrorFormat("Failed to load the configuration file {0}", conf.path);
                enabled = false;
                return;
            }

            switch (mode)
            {
            case PIPELINE.Fiducial:
                pipeline = new FiducialPipeline(xpcfComponentManager).AddTo(subscriptions);
                break;

            case PIPELINE.Natural:
                pipeline = new NaturalPipeline(xpcfComponentManager).AddTo(subscriptions);
                break;

            case PIPELINE.SLAM:
                pipeline = new SlamPipeline(xpcfComponentManager).AddTo(subscriptions);
                break;
            }

            overlay3D = xpcfComponentManager.Create("SolAR3DOverlayOpencv").BindTo <I3DOverlay>().AddTo(subscriptions);

            switch (source)
            {
            case SOURCE.SolAR:
                camera = xpcfComponentManager.Create("SolARCameraOpencv").BindTo <ICamera>().AddTo(subscriptions);

                var intrinsic  = camera.getIntrinsicsParameters();
                var distorsion = camera.getDistorsionParameters();
                var resolution = camera.getResolution();
                pipeline.SetCameraParameters(intrinsic, distorsion);
                overlay3D.setCameraParameters(intrinsic, distorsion);
                OnCalibrate?.Invoke(resolution, intrinsic, distorsion);

                if (camera.start() != FrameworkReturnCode._SUCCESS)
                {
                    LOG_ERROR("Camera cannot start");
                    enabled = false;
                }
                break;

            case SOURCE.Unity:
                webcam = new WebCamTexture();
                webcam.Play();
                if (!webcam.isPlaying)
                {
                    LOG_ERROR("Camera cannot start");
                    enabled = false;
                }
                break;
            }

            switch (display)
            {
            case DISPLAY.SolAR:
                // Set the size of the box to display according to the marker size in world unit
                var overlay3D_sizeProp = overlay3D.BindTo <IConfigurable>().getProperty("size");
                var size = pipeline.GetMarkerSize();
                overlay3D_sizeProp.setFloatingValue(size.width, 0);
                overlay3D_sizeProp.setFloatingValue(size.height, 1);
                overlay3D_sizeProp.setFloatingValue(size.height / 2.0f, 2);

                imageViewer = xpcfComponentManager.Create("SolARImageViewerOpencv").AddTo(subscriptions).BindTo <IImageViewer>().AddTo(subscriptions);
                break;

            case DISPLAY.Unity:
                break;
            }

            start = clock();

            inputImage = SharedPtr.Alloc <Image>().AddTo(subscriptions);
            pose       = new Transform3Df().AddTo(subscriptions);
        }
 public static IDisposable RegisterGenericMotion(this View view, EventHandler <View.GenericMotionEventArgs> handler)
 {
     view.GenericMotion += handler;
     return(Disposable.Create(() => view.RunIfNativeInstanceAvailable(v => v.GenericMotion -= handler)));
 }
示例#25
0
 public IDisposable Subscribe(IObserver <bool> observer)
 {
     ProducerObserver = observer;
     NotifyProducer();
     return(Disposable.Create(() => ProducerObserver = null));
 }