/// <summary> /// Dispatches a packet /// </summary> /// <param name="packet"></param> /// <param name="cancellationToken"></param> /// <remarks> /// This dispatcher is fault tolerant to: /// 1. Null packets /// 2. Packets with no associated handlers /// 3. Packets which cannot be handled by their registered handlers /// /// This dispatcher is thread-safe as long as the loggers and handlers are thread-safe. /// We use correlation ids when we emit errors to match our packet log with our /// general log stream. /// /// This sample includes a "bug" on packets of a ID 5 to illustrate how exceptions /// in the dispatcher itself are handled by the caller. /// </remarks> public async Task DispatchAsync(IPacket packet, CancellationToken cancellationToken) { if (packet == null) { throw new ArgumentNullException(nameof(packet)); } if (packet.Id == 5) { throw new Exception("Oh noes, a bug in the dispatcher!"); } if (_handlers.TryGetValue(packet.Id, out var handler)) { try { await handler.ProcessPacketAsync(packet, cancellationToken); } catch (Exception ex) { Guid correlationId = Guid.NewGuid(); _logger.Error(ex, "CID:{correlationId} Failed handling packet with id {packetId} and type {packetType}", correlationId.ToString(), packet.Id, packet.GetType().Name); _packetLogger.LogPacket(PacketLogReason.Error, correlationId, packet); } } else { // Non-existent handler case Guid correlationId = Guid.NewGuid(); _logger.Error( "CID:{correlationId} Unexpected packet with id {packetId} and type {packetType}", correlationId.ToString(), packet.Id, packet.GetType().Name); _packetLogger.LogPacket(PacketLogReason.Error, correlationId, packet); } }
/// <summary> /// The main packet processing loop. /// </summary> /// <remarks> /// This method illustrates how the dispatcher can be used. For this illustration /// we use an async foreach, to simulate a thread which receives and processes packets /// independent of the rest of the process, and which responds to cancellation tokens /// by terminating any outstanding dispatches and terminating the waits on any /// incoming packets. /// </remarks> static async Task ProcessPacketsAsync( IPacketDispatcher dispatcher, IPacketLogger packetLogger, CancellationToken cancellationToken) { await foreach (var packet in GeneratePacketsAsync(cancellationToken)) { if (cancellationToken.IsCancellationRequested) { return; } try { await dispatcher.DispatchAsync(packet, cancellationToken); } catch (Exception ex) { Guid correlationId = Guid.NewGuid(); Log.Error(ex, "CID:{correlationId}: Exception invoking dispatcher for packet with id {id} and type {type}", correlationId, packet?.Id, packet?.GetType().Name); packetLogger.LogPacket(PacketLogReason.Error, correlationId, packet); } } }