public IObservable <Response> Send( [NotNull] Request request, CancellationToken token = default(CancellationToken)) { OverlappingPipeClientStream stream = _stream; if (_state != PipeState.Connected || stream == null) { // ReSharper disable once AssignNullToNotNullAttribute return(Observable.Empty <Response>()); } // ReSharper disable once AssignNullToNotNullAttribute return(Observable.Create <Response>( async(observer, t) => { Debug.Assert(observer != null); using (ITokenSource tokenSource = token.CreateLinked(t)) { token = tokenSource.Token; ConnectedCommand cr = new ConnectedCommand(request, observer); _commandRequests.TryAdd(request.ID, cr); try { await stream.WriteAsync(request.Serialize(), token).ConfigureAwait(false); await cr.CompletionTask.WithCancellation(token).ConfigureAwait(false); } // ReSharper disable once EmptyGeneralCatchClause catch { } // If the command is not explicitly cancelled and is still running, and we've been cancelled // then ask the server to cancel. if (!cr.IsCancelled && !cr.IsCompleted && token.IsCancellationRequested) { try { using (CancellationTokenSource cts = Constants.FireAndForgetTokenSource) await CancelCommand(request.ID, cts.Token).ConfigureAwait(false); } catch (TaskCanceledException) { } } // Remove the command request. _commandRequests.TryRemove(request.ID, out cr); } })); }
/// <summary> /// Initializes a new instance of the <see cref="NamedPipeClient" /> class. /// </summary> /// <param name="description">The client description.</param> /// <param name="server">The server.</param> /// <param name="onReceive">The action to call on receipt of a message.</param> /// <param name="token">The token.</param> private NamedPipeClient( [NotNull] string description, [NotNull] NamedPipeServerInfo server, [NotNull] Action <Message> onReceive, CancellationToken token = default(CancellationToken)) { if (description == null) { throw new ArgumentNullException("description"); } if (server == null) { throw new ArgumentNullException("server"); } if (onReceive == null) { throw new ArgumentNullException("onReceive"); } _server = server; _cancellationTokenSource = new CancellationTokenSource(); CancellationToken disposeToken = _cancellationTokenSource.Token; _clientTask = Task.Run( async() => { try { using (ITokenSource tokenSource = token.CreateLinked(disposeToken)) using ( OverlappingPipeClientStream stream = new OverlappingPipeClientStream( _server.Host, _server.FullName, PipeTransmissionMode.Message)) { _state = PipeState.Open; token = tokenSource.Token; // We need to support cancelling the connect. await stream.Connect(token).ConfigureAwait(false); ConnectResponse connectResponse = null; DisconnectResponse disconnectResponse = null; if (!token.IsCancellationRequested) { // Set the stream. _stream = stream; _state = PipeState.AwaitingConnect; // Kick off a connect request, but don't wait for it's result as we're the task that will receive it! ConnectRequest connectRequest = new ConnectRequest(description); await stream.WriteAsync(connectRequest.Serialize(), token).ConfigureAwait(false); // Keep going as long as we're connected. try { while (stream.IsConnected && !disposeToken.IsCancellationRequested) { // Read data in. byte[] data = await stream.ReadAsync(disposeToken).ConfigureAwait(false); if (data == null) { break; } // Deserialize the incoming message. Message message = Message.Deserialize(data); if (connectResponse == null) { // We require a connect response to start connectResponse = message as ConnectResponse; if (connectResponse == null || connectResponse.ID != connectRequest.ID) { break; } _state = PipeState.Connected; _serviceName = connectResponse.ServiceName; Log.Add( LoggingLevel.Notification, () => ClientResources.Not_NamedPipeClient_Connection, connectResponse.ServiceName); TaskCompletionSource <NamedPipeClient> ccs = Interlocked.Exchange(ref _connectionCompletionSource, null); if (ccs != null) { ccs.TrySetResult(this); } // Observer the message. onReceive(message); continue; } // Check for disconnect, we don't observe the message until the disconnect is complete. disconnectResponse = message as DisconnectResponse; if (disconnectResponse != null) { break; } // Observe the message. onReceive(message); Response response = message as Response; if (response == null) { continue; } ConnectedCommand connectedCommand; // Check for cancellation responses. CommandCancelResponse cancelResponse = response as CommandCancelResponse; if (cancelResponse != null) { // Cancel the associated request if (_commandRequests.TryGetValue( cancelResponse.CancelledCommandId, out connectedCommand)) { // ReSharper disable once PossibleNullReferenceException connectedCommand.Cancel(cancelResponse); } } // And fall through to complete the response... // Find command the response is related to, and notify it of the response. if (!_commandRequests.TryGetValue(response.ID, out connectedCommand)) { continue; } Debug.Assert(connectedCommand != null); if (connectedCommand.Received(response)) { _commandRequests.TryRemove(response.ID, out connectedCommand); } } } catch (TaskCanceledException) { } } // If we're still connected, and we haven't received a disconnect response, try to send a disconnect request. if (stream.IsConnected && disconnectResponse == null) { CancellationTokenSource cts = token.IsCancellationRequested ? new CancellationTokenSource(500) : null; try { CancellationToken t = cts != null ? cts.Token : token; // Try to send disconnect request. // ReSharper disable once PossibleNullReferenceException await Send(new DisconnectRequest(), t) .ToTask(t) .ConfigureAwait(false); } catch (TaskCanceledException) { } finally { if (cts != null) { cts.Dispose(); } } } // Remove the stream. _stream = null; _state = PipeState.Closed; _serviceName = null; // If we had a disconnect message observe it now that the disconnect has been actioned, // this prevents the receiver thinking the connection is still active. if (disconnectResponse != null) { onReceive(disconnectResponse); ConnectedCommand connectedCommand; if (_commandRequests.TryGetValue(disconnectResponse.ID, out connectedCommand)) { Debug.Assert(connectedCommand != null); if (connectedCommand.Received(disconnectResponse)) { _commandRequests.TryRemove(disconnectResponse.ID, out connectedCommand); } } } } } catch (IOException ioe) { if (!token.IsCancellationRequested) { // Common exception caused by sudden disconnect, lower level Log.Add( ioe, LoggingLevel.Information, () => ClientResources.Err_NamedPipeClient_Failed); } } catch (Exception exception) { TaskCanceledException tce = exception as TaskCanceledException; TaskCompletionSource <NamedPipeClient> ccs = Interlocked.Exchange( ref _connectionCompletionSource, null); if (ccs != null) { if (tce != null) { ccs.TrySetCanceled(); } else { ccs.TrySetException(exception); } } // We only log if this wasn't a cancellation exception. if (tce == null && !token.IsCancellationRequested) { Log.Add( exception, LoggingLevel.Error, () => ClientResources.Err_NamedPipeClient_Failed); } } finally { Dispose(); } }, disposeToken); }