Example #1
0
        public async Task StartAsync(CancellationToken ct = default(CancellationToken))
        {
            if (_hasStarted)
            {
                throw new MorseLException($"Cannot call {nameof(StartAsync)} more than once.");
            }
            _hasStarted = true;

            ct.Register(() => _connectionCts.TrySetCanceled(ct));

            await Task.Run(async() =>
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                await _clientWebSocket.ConnectAsync(new Uri(_uri), ct).ConfigureAwait(false);
                stopwatch.Stop();

                _logger.LogDebug($"Connection to {_uri} established after {stopwatch.Elapsed.ToString("mm\\:ss\\.ff")}");

                _receiveLoopTask = Receive(async stream =>
                {
                    _logger.LogTrace($"Received message stream - beginning processing");
                    var message = await MessageSerializer.DeserializeAsync <Message>(stream, Encoding.UTF8).ConfigureAwait(false);
                    _logger.LogTrace($"Received \"{message}\"");

                    switch (message.MessageType)
                    {
                    case MessageType.ConnectionEvent:
                        _connectionCts.TrySetResult(null);

                        ConnectionId = message.Data;
                        Connected?.Invoke();
                        break;

                    case MessageType.ClientMethodInvocation:
                        InvocationDescriptor invocationDescriptor = null;
                        try
                        {
                            invocationDescriptor = MessageSerializer.DeserializeInvocationDescriptor(message.Data, _handlers);
                        }
                        catch (Exception exception)
                        {
                            await HandleUnparseableReceivedInvocationDescriptor(message.Data, exception).ConfigureAwait(false);
                            return;
                        }

                        if (invocationDescriptor == null)
                        {
                            // Valid JSON but unparseable into a known, typed invocation descriptor (unknown method name, invalid parameters)
                            await HandleInvalidReceivedInvocationDescriptor(message.Data).ConfigureAwait(false);
                            return;
                        }

                        await InvokeOn(invocationDescriptor).ConfigureAwait(false);
                        break;

                    case MessageType.InvocationResult:
                        InvocationResultDescriptor resultDescriptor;
                        try
                        {
                            resultDescriptor = MessageSerializer.DeserializeInvocationResultDescriptor(message.Data, _pendingCalls);
                        }
                        catch (Exception exception)
                        {
                            await HandleInvalidInvocationResultDescriptor(message.Data, exception).ConfigureAwait(false);
                            return;
                        }

                        HandleInvokeResult(resultDescriptor);
                        break;

                    case MessageType.Error:
                        await HandleErrorMessage(message).ConfigureAwait(false);
                        break;
                    }
                }, ct);
                _receiveLoopTask.ContinueWith(task =>
                {
                    if (task.IsFaulted)
                    {
                        Error?.Invoke(task.Exception);
                    }
                });
            }, ct).ConfigureAwait(false);

            var timeoutTask = Task.Delay(_options.ConnectionTimeout);
            await Task.WhenAny(_connectionCts.Task, timeoutTask).ConfigureAwait(false);

            if (!ct.IsCancellationRequested && !_connectionCts.Task.IsCompleted && timeoutTask.IsCompleted)
            {
                throw new TimeoutException($"Connection attempt to {_uri} timed out after {_options.ConnectionTimeout}");
            }
        }