public BitConverterHelper(TcpClientIoOptions options) { _options = options; _customConverters = options.Converters.Select(converter => { var converterType = converter.GetType(); var type = converterType.BaseType; if (type == null) { throw TcpClientIoException.ConverterError(converterType.Name); } var genericType = type.GenericTypeArguments.Single(); return(new KeyValuePair <Type, TcpConverter>(genericType, converter)); }).ToDictionary(pair => pair.Key, pair => pair.Value); _builtInConvertersToBytes = new Dictionary <Type, MethodInfo> { { typeof(bool), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(bool) }) }, { typeof(char), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(char) }) }, { typeof(double), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(double) }) }, { typeof(short), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(short) }) }, { typeof(int), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(int) }) }, { typeof(long), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(long) }) }, { typeof(float), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(float) }) }, { typeof(ushort), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(ushort) }) }, { typeof(uint), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(uint) }) }, { typeof(ulong), typeof(BitConverter).GetMethod(nameof(BitConverter.GetBytes), new[] { typeof(ulong) }) } }; }
/// <summary> /// Begins an asynchronous request to receive response associated with the specified ID from a connected <see cref="TcpClientIo{TId,TRequest,TResponse}"/> object. /// </summary> /// <param name="responseId"></param> /// <param name="token"></param> /// <returns><see cref="ITcpBatch{TResponse}"/></returns> /// <exception cref="TcpClientIoException"></exception> public async Task <ITcpBatch <TResponse> > ReceiveAsync(TId responseId, CancellationToken token = default) { if (_disposing) { throw new ObjectDisposedException(nameof(_tcpClient)); } if (!_disposing && _pipelineReadEnded) { throw TcpClientIoException.ConnectionBroken(); } return(await _completeResponses.WaitAsync(responseId, token)); }
private void SetupTasks() { _ = TcpWriteAsync(); _ = TcpReadAsync().ContinueWith(antecedent => { if (_disposing || !_pipelineReadEnded) { return; } foreach (var kv in _completeResponses.ToArray()) { kv.Value.TrySetException(TcpClientIoException.ConnectionBroken()); } }, TaskContinuationOptions.OnlyOnRanToCompletion); _ = DeserializeResponseAsync(); }
private TcpClientIo(TcpClientIoOptions tcpClientIoOptions, ILogger <TcpClientIo <TId, TRequest, TResponse> > logger) { var pipe = new Pipe(); Id = Guid.NewGuid(); _logger = logger; _options = tcpClientIoOptions ?? TcpClientIoOptions.Default; _batchRules = TcpBatchRules <TResponse> .Default; _baseCancellationTokenSource = new CancellationTokenSource(); _baseCancellationToken = _baseCancellationTokenSource.Token; _bufferBlockRequests = new BufferBlock <SerializedRequest>(); var middleware = new MiddlewareBuilder <ITcpBatch <TResponse> >() .RegisterCancellationActionInWait((tcs, hasOwnToken) => { if (_disposing || hasOwnToken) { tcs.TrySetCanceled(); } else if (!_disposing && _pipelineReadEnded) { tcs.TrySetException(TcpClientIoException.ConnectionBroken()); } }) .RegisterDuplicateActionInSet((batch, newBatch) => _batchRules.Update(batch, newBatch.Single())) .RegisterCompletionActionInSet(() => _consumingResetEvent.Set()); _completeResponses = new WaitingDictionary <TId, ITcpBatch <TResponse> >(middleware); _arrayPool = ArrayPool <byte> .Create(); var bitConverterHelper = new BitConverterHelper(_options); _serializer = new TcpSerializer <TRequest>(bitConverterHelper, length => _arrayPool.Rent(length)); _deserializer = new TcpDeserializer <TId, TResponse>(bitConverterHelper); _writeResetEvent = new AsyncManualResetEvent(); _readResetEvent = new AsyncManualResetEvent(); _consumingResetEvent = new AsyncManualResetEvent(); _deserializePipeReader = pipe.Reader; _deserializePipeWriter = pipe.Writer; }
/// <summary> /// Serialize and sends data asynchronously to a connected <see cref="TcpClientIo{TRequest,TResponse}"/> object. /// </summary> /// <param name="request"></param> /// <param name="token"></param> /// <returns><see cref="bool"/></returns> /// <exception cref="TcpClientIoException"></exception> public async Task <bool> SendAsync(TRequest request, CancellationToken token = default) { try { if (_disposing) { throw new ObjectDisposedException(nameof(_tcpClient)); } if (!_disposing && _pipelineWriteEnded) { throw TcpClientIoException.ConnectionBroken(); } var serializedRequest = _serializer.Serialize(request); return(await _bufferBlockRequests.SendAsync(serializedRequest, token == default?_baseCancellationToken : token)); } catch (Exception e) { _logger?.LogError($"{nameof(SendAsync)} Got {e.GetType()}: {e.Message}"); throw; } }
/// <summary>Provides a consuming <see cref="T:System.Collections.Generics.IAsyncEnumerable{T}"/> for <see cref="ITcpBatch{TResponse}"/> in the collection. /// Calling MoveNextAsync on the returned enumerable will block if there is no data available, or will /// throw an <see cref="System.OperationCanceledException"/> if the <see cref="CancellationToken"/> is canceled. /// </summary> /// <param name="token">A cancellation token to observe.</param> /// <returns></returns> /// <exception cref="OperationCanceledException">If the <see cref="CancellationToken"/> is canceled.</exception> /// <exception cref="TcpClientIoException"></exception> public async IAsyncEnumerable <ITcpBatch <TResponse> > GetConsumingAsyncEnumerable([EnumeratorCancellation] CancellationToken token = default) { CancellationTokenSource internalCts = null; CancellationToken internalToken; if (token != default) { internalCts = CancellationTokenSource.CreateLinkedTokenSource(_baseCancellationToken, token); internalToken = internalCts.Token; } else { internalToken = _baseCancellationToken; } while (!internalToken.IsCancellationRequested) { IList <ITcpBatch <TResponse> > result; try { internalToken.ThrowIfCancellationRequested(); var completedResponses = _completeResponses .Filter(p => p.Value.Task.Status == TaskStatus.RanToCompletion) .ToArray(); if (completedResponses.Length == 0) { await _consumingResetEvent.WaitAsync(internalToken); _consumingResetEvent.Reset(); continue; } result = new List <ITcpBatch <TResponse> >(); foreach (var(key, tcs) in completedResponses) { internalToken.ThrowIfCancellationRequested(); if (await _completeResponses.TryRemoveAsync(key)) { result.Add(await tcs.Task); } } } catch (OperationCanceledException) { if (!_disposing && _pipelineReadEnded) { throw TcpClientIoException.ConnectionBroken(); } throw; } catch (Exception exception) { _logger?.LogCritical($"{nameof(GetConsumingAsyncEnumerable)} Got {exception.GetType()}, {exception}"); throw; } foreach (var batch in result) { yield return(batch); } } internalCts?.Dispose(); }