Esempio n. 1
0
        /// <summary>
        /// Start polling the node-dss server with a GET request, and continue to do so
        /// until <see cref="StopPollingAsync"/> is called.
        /// </summary>
        /// <returns>Returns <c>true</c> if polling effectively started with this call.</returns>
        /// <remarks>
        /// The <see cref="LocalPeerId"/> field must be set before calling this method.
        /// This method can safely be called multiple times, and will do nothing if
        /// polling is already underway or waiting to be stopped.
        /// </remarks>
        public bool StartPollingAsync(CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(LocalPeerId))
            {
                throw new InvalidOperationException("Cannot start polling with empty LocalId.");
            }

            lock (_pollingLock)
            {
                if (_isPolling)
                {
                    return(false);
                }
                _isPolling = true;
                _cancellationTokenSource = new CancellationTokenSource();
            }
            RaisePropertyChanged("IsPolling");

            // Build the GET polling request
            string requestUri = $"{_httpServerAddress}data/{LocalPeerId}";

            long lastPollTimeTicks = DateTime.UtcNow.Ticks;
            long pollTimeTicks     = TimeSpan.FromMilliseconds(PollTimeMs).Ticks;

            var masterTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token, cancellationToken);
            var masterToken       = masterTokenSource.Token;

            masterToken.Register(() =>
            {
                lock (_pollingLock)
                {
                    _isPolling = false;
                    _cancellationTokenSource.Dispose();
                    _cancellationTokenSource = null;
                }
                RaisePropertyChanged("IsPolling");
                Interlocked.Exchange(ref _connectedEventFired, 0);
                OnDisconnect?.Invoke();
                OnPollingDone?.Invoke();
                masterTokenSource.Dispose();
            });

            // Prepare the repeating poll task.
            // In order to poll at the specified frequency but also avoid overlapping requests,
            // use a repeating task which re-schedule itself on completion, either immediately
            // if the polling delay is elapsed, or at a later time otherwise.
            async void PollServer()
            {
                try
                {
                    // Polling loop
                    while (true)
                    {
                        masterToken.ThrowIfCancellationRequested();

                        // Send GET request to DSS server.
                        lastPollTimeTicks = DateTime.UtcNow.Ticks;
                        HttpResponseMessage response = await _httpClient.GetAsync(requestUri,
                                                                                  HttpCompletionOption.ResponseHeadersRead, masterToken);

                        // On first successful HTTP request, raise the connected event
                        if (0 == Interlocked.Exchange(ref _connectedEventFired, 1))
                        {
                            OnConnect?.Invoke();
                        }

                        masterToken.ThrowIfCancellationRequested();

                        // In order to avoid exceptions in GetStreamAsync() when the server returns a non-success status code (e.g. 404),
                        // first get the HTTP headers, check the status code, then if successful wait for content.
                        if (response.IsSuccessStatusCode)
                        {
                            string jsonMsg = await response.Content.ReadAsStringAsync();

                            masterToken.ThrowIfCancellationRequested();

                            var jsonSettings = new JsonSerializerSettings
                            {
                                Error = (object s, ErrorEventArgs e) => throw new Exception("JSON error: " + e.ErrorContext.Error.Message)
                            };
                            Message msg = JsonConvert.DeserializeObject <Message>(jsonMsg, jsonSettings);
                            if (msg != null)
                            {
                                OnMessage?.Invoke(msg);
                            }
                            else
                            {
                                throw new Exception("Failed to deserialize signaler message from JSON.");
                            }
                        }

                        masterToken.ThrowIfCancellationRequested();

                        // Delay next loop iteration if current polling was faster than target poll duration
                        long curTime     = DateTime.UtcNow.Ticks;
                        long deltaTicks  = curTime - lastPollTimeTicks;
                        long remainTicks = pollTimeTicks - deltaTicks;
                        if (remainTicks > 0)
                        {
                            int waitTimeMs = new TimeSpan(remainTicks).Milliseconds;
                            await Task.Delay(waitTimeMs);
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    // Manual cancellation via UI, do not report error
                }
                catch (Exception ex)
                {
                    OnFailure?.Invoke(ex);
                }
            }

            // Start the poll task immediately
            Task.Run(PollServer, masterToken);
            return(true);
        }
Esempio n. 2
0
        /// <summary>
        /// Start polling the node-dss server with a GET request, and continue to do so
        /// until <see cref="StopPollingAsync"/> is called.
        /// </summary>
        /// <returns>Returns <c>true</c> if polling effectively started with this call.</returns>
        /// <remarks>
        /// The <see cref="LocalPeerId"/> field must be set before calling this method.
        /// This method can safely be called multiple times, and will do nothing if
        /// polling is already underway or waiting to be stopped.
        /// </remarks>
        public bool StartPollingAsync()
        {
            if (string.IsNullOrWhiteSpace(LocalPeerId))
            {
                throw new Exception("Cannot start polling with empty LocalId.");
            }

            if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 1)
            {
                // Already polling
                return(false);
            }

            // Build the GET polling request
            string requestUri = $"{_httpServerAddress}data/{LocalPeerId}";

            long lastPollTimeTicks = DateTime.UtcNow.Ticks;
            long pollTimeTicks     = TimeSpan.FromMilliseconds(PollTimeMs).Ticks;

            _cancellationTokenSource = new CancellationTokenSource();
            var token = _cancellationTokenSource.Token;

            token.Register(() =>
            {
                Interlocked.Exchange(ref _isPolling, 0);
                // TODO - Potentially a race condition here if StartPollingAsync() called,
                //        but would be even worse with the exchange being after, as
                //        StopPollingAsync() would attempt to read from it while being disposed.
                _cancellationTokenSource.Dispose();
                _cancellationTokenSource = null;
                OnPollingDone?.Invoke();
            });

            // Prepare the repeating poll task.
            // In order to poll at the specified frequency but also avoid overlapping requests,
            // use a repeating task which re-schedule itself on completion, either immediately
            // if the polling delay is elapsed, or at a later time otherwise.
            Action nextAction = null;

            nextAction = () =>
            {
                token.ThrowIfCancellationRequested();

                // Send GET request to DSS server.
                // In order to avoid exceptions in GetStreamAsync() when the server returns a non-success status code (e.g. 404),
                // first get the HTTP headers, check the status code, then if successful wait for content.
                lastPollTimeTicks = DateTime.UtcNow.Ticks;
                _httpClient.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead).ContinueWith((getTask) =>
                {
                    if (getTask.Exception != null)
                    {
                        OnFailure?.Invoke(getTask.Exception);
                        return;
                    }

                    token.ThrowIfCancellationRequested();

                    HttpResponseMessage response = getTask.Result;
                    if (response.IsSuccessStatusCode)
                    {
                        response.Content.ReadAsStringAsync().ContinueWith((readTask) =>
                        {
                            if (readTask.Exception != null)
                            {
                                OnFailure?.Invoke(readTask.Exception);
                                return;
                            }

                            token.ThrowIfCancellationRequested();

                            var jsonMsg = readTask.Result;
                            var msg     = JsonConvert.DeserializeObject <Message>(jsonMsg);
                            if (msg != null)
                            {
                                OnMessage?.Invoke(msg);
                            }
                            else
                            {
                                OnFailure?.Invoke(new Exception($"Failed to deserialize SignalerMessage object from JSON."));
                            }
                        });
                    }

                    // Some time may have passed waiting for GET content; check token again for responsiveness
                    token.ThrowIfCancellationRequested();

                    // Repeat task to continue polling
                    long curTime    = DateTime.UtcNow.Ticks;
                    long deltaTicks = curTime - lastPollTimeTicks;
                    if (deltaTicks >= pollTimeTicks)
                    {
                        // Previous GET task took more time than polling delay, execute ASAP
                        Task.Run(nextAction, token);
                    }
                    else
                    {
                        // Previous GET task took less time than polling delay, schedule next polling
                        long remainTicks       = pollTimeTicks - deltaTicks;
                        int nextScheduleTimeMs = (int)(new TimeSpan(remainTicks).TotalMilliseconds);
                        Task.Delay(nextScheduleTimeMs, token).ContinueWith(_ => nextAction(), token);
                    }
                });
            };

            // Start the first poll task immediately
            Task.Run(nextAction, token);
            return(true);
        }