public void NamedPipeActivityTest() { const string pipeName = "test.pipe"; var logger = XUnitLogger.CreateLogger(_testOutput); // Arrange var activity = new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "unknown", ChannelId = "test", Text = "hi" }; var bot = new StreamingTestBot((turnContext, cancellationToken) => { var activityClone = JsonConvert.DeserializeObject <Activity>(JsonConvert.SerializeObject(turnContext.Activity)); activityClone.Text = $"Echo: {turnContext.Activity.Text}"; return(turnContext.SendActivityAsync(activityClone, cancellationToken)); }); var verifiedResponse = false; var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(h => h.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), It.IsAny <ILogger <RequestHandler> >(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .Returns <ReceiveRequest, ILogger <RequestHandler>, object, CancellationToken>((request, anonLogger, context, cancellationToken) => { var body = request.ReadBodyAsString(); var response = JsonConvert.DeserializeObject <Activity>(body, SerializationSettings.DefaultDeserializationSettings); Assert.NotNull(response); Assert.Equal("Echo: hi", response.Text); verifiedResponse = true; return(Task.FromResult(StreamingResponse.OK())); }); // Act var server = new CloudAdapter(new StreamingTestBotFrameworkAuthentication(), logger); var serverRunning = server.ConnectNamedPipeAsync(pipeName, bot, "testAppId", "testAudience", "testCallerId"); var client = new NamedPipeClient(pipeName, ".", clientRequestHandler.Object, logger: logger); var clientRunning = client.ConnectAsync(); SimulateMultiTurnConversation(1, new[] { activity }, client, logger); // Assert Assert.True(verifiedResponse); }
public void ServerTransportCanSendMessages() { var logger = XUnitLogger.CreateLogger(_testOutput); using (var connection = new TestWebSocketConnectionFeature()) { var server = connection.AcceptAsync().GetAwaiter().GetResult(); var client = connection.Client; var receiverRunning = ReceiveAsync(client); var fromTransport = new Pipe(PipeOptions.Default); var toTransport = new Pipe(PipeOptions.Default); var webSocketManager = new Mock <WebSocketManager>(); webSocketManager.Setup(m => m.AcceptWebSocketAsync()).Returns(Task.FromResult(server)); var httpContext = new Mock <HttpContext>(); httpContext.Setup(c => c.WebSockets).Returns(webSocketManager.Object); var sut = new WebSocketTransport(httpContext.Object.WebSockets.AcceptWebSocketAsync().GetAwaiter().GetResult(), new DuplexPipe(toTransport.Reader, fromTransport.Writer), logger); var serverTransportRunning = sut.ConnectAsync(CancellationToken.None); var messages = new List <byte[]> { Encoding.UTF8.GetBytes("foo") }; WriteAsync(toTransport.Writer, messages).Wait(); toTransport.Writer.CompleteAsync().GetAwaiter().GetResult(); serverTransportRunning.Wait(); var output = receiverRunning.GetAwaiter().GetResult(); Assert.Equal("foo", Encoding.UTF8.GetString(output[0])); } }
public void ClientTransportCanSendMessages() { var logger = XUnitLogger.CreateLogger(_testOutput); using (var connection = new TestWebSocketConnectionFeature()) { var server = connection.AcceptAsync().GetAwaiter().GetResult(); var client = connection.Client; var receiverRunning = ReceiveAsync(server); var fromTransport = new Pipe(PipeOptions.Default); var toTransport = new Pipe(PipeOptions.Default); var sut = new WebSocketTransport(client, new DuplexPipe(toTransport.Reader, fromTransport.Writer), logger); var clientTransportRunning = sut.ConnectAsync(CancellationToken.None); var messages = new List <byte[]> { Encoding.UTF8.GetBytes("foo") }; WriteAsync(toTransport.Writer, messages).Wait(); toTransport.Writer.CompleteAsync().GetAwaiter().GetResult(); clientTransportRunning.Wait(); var output = receiverRunning.GetAwaiter().GetResult(); Assert.Equal("foo", Encoding.UTF8.GetString(output[0])); } }
public async Task ServerTransportCanReceiveMessages() { var logger = XUnitLogger.CreateLogger(_testOutput); using (var connection = new TestWebSocketConnectionFeature()) { var server = connection.AcceptAsync(); var client = connection.Client; var fromTransport = new Pipe(PipeOptions.Default); var toTransport = new Pipe(PipeOptions.Default); var listenerRunning = ListenAsync(fromTransport.Reader); var webSocketManager = new Mock <WebSocketManager>(); webSocketManager.Setup(m => m.AcceptWebSocketAsync()).Returns(server); var httpContext = new Mock <HttpContext>(); httpContext.Setup(c => c.WebSockets).Returns(webSocketManager.Object); var sut = new WebSocketTransport(httpContext.Object.WebSockets.AcceptWebSocketAsync().GetAwaiter().GetResult(), new DuplexPipe(toTransport.Reader, fromTransport.Writer), logger); var serverTransportRunning = sut.ConnectAsync(CancellationToken.None); var messages = new List <byte[]> { Encoding.UTF8.GetBytes("foo"), Encoding.UTF8.GetBytes("bar") }; SendBinaryAsync(client, messages).Wait(); client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done sending.", CancellationToken.None).Wait(); serverTransportRunning.Wait(); var output = await listenerRunning; Assert.Equal("foo", Encoding.UTF8.GetString(output[0])); Assert.Equal("bar", Encoding.UTF8.GetString(output[1])); } }
public void ClientTransportCanReceiveMessages() { var logger = XUnitLogger.CreateLogger(_testOutput); using (var connection = new TestWebSocketConnectionFeature()) { var server = connection.AcceptAsync().GetAwaiter().GetResult(); var client = connection.Client; var fromTransport = new Pipe(PipeOptions.Default); var toTransport = new Pipe(PipeOptions.Default); var listenerRunning = ListenAsync(fromTransport.Reader); var sut = new WebSocketTransport(new DuplexPipe(toTransport.Reader, fromTransport.Writer), logger); var clientTransportRunning = sut.ProcessSocketAsync(client, CancellationToken.None); var messages = new List <byte[]> { Encoding.UTF8.GetBytes("foo") }; SendBinaryAsync(server, messages).Wait(); server.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done sending.", CancellationToken.None).Wait(); clientTransportRunning.Wait(); var output = listenerRunning.GetAwaiter().GetResult(); Assert.Equal("foo", Encoding.UTF8.GetString(output[0])); } }
public async Task Integration_KeepAlive() { // TODO: Transform this test into a theory and do multi-message, multi-thread, multi-client, etc. var logger = XUnitLogger.CreateLogger(_outputHelper); var cts = new CancellationTokenSource(); using (var webSocketFeature = new TestWebSocketConnectionFeature()) { // Bot / server setup var botRequestHandler = new Mock <RequestHandler>(); botRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var socket = await webSocketFeature.AcceptAsync().ConfigureAwait(false); var connection = new WebSocketStreamingConnection(socket, logger); var serverTask = connection.ListenAsync(botRequestHandler.Object, cts.Token); // Client / channel setup var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var client = new WebSocketClient(webSocketFeature.Client, "wss://test", clientRequestHandler.Object, logger: logger, closeTimeOut: TimeSpan.FromSeconds(10), keepAlive: TimeSpan.FromMilliseconds(200)); var clientTask = client.ConnectInternalAsync(CancellationToken.None); // Send request bot (server) -> channel (client) const string path = "api/version"; const string botToClientPayload = "Hello human, I'm Bender!"; var request = StreamingRequest.CreatePost(path, new StringContent(botToClientPayload)); var responseFromClient = await connection.SendStreamingRequestAsync(request).ConfigureAwait(false); Assert.Equal(200, responseFromClient.StatusCode); const string clientToBotPayload = "Hello bot, I'm Calculon!"; var clientRequest = StreamingRequest.CreatePost(path, new StringContent(clientToBotPayload)); // Send request bot channel (client) -> (server) var clientToBotResult = await client.SendAsync(clientRequest).ConfigureAwait(false); Assert.Equal(200, clientToBotResult.StatusCode); await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false); Assert.True(client.IsConnected); } }
public async Task Integration_Interop_LegacyClient() { // TODO: Transform this test into a theory and do multi-message, multi-thread, multi-client, etc. var logger = XUnitLogger.CreateLogger(_outputHelper); using (var webSocketFeature = new TestWebSocketConnectionFeature()) { // Bot / server setup var botRequestHandler = new Mock <RequestHandler>(); botRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var socket = await webSocketFeature.AcceptAsync().ConfigureAwait(false); var connection = new WebSocketStreamingConnection(socket, logger); var serverTask = Task.Run(() => connection.ListenAsync(botRequestHandler.Object)); // Client / channel setup var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); using (var client = new Microsoft.Bot.Streaming.Transport.WebSockets.WebSocketClient("wss://test", clientRequestHandler.Object)) { await client.ConnectInternalAsync(webSocketFeature.Client).ConfigureAwait(false); // Send request bot (server) -> channel (client) const string path = "api/version"; const string botToClientPayload = "Hello human, I'm Bender!"; var request = StreamingRequest.CreatePost(path, new StringContent(botToClientPayload)); var responseFromClient = await connection.SendStreamingRequestAsync(request).ConfigureAwait(false); Assert.Equal(200, responseFromClient.StatusCode); const string clientToBotPayload = "Hello bot, I'm Calculon!"; var clientRequest = StreamingRequest.CreatePost(path, new StringContent(clientToBotPayload)); // Send request bot channel (client) -> (server) var clientToBotResult = await client.SendAsync(clientRequest).ConfigureAwait(false); Assert.Equal(200, clientToBotResult.StatusCode); client.Disconnect(); } await serverTask.ConfigureAwait(false); } }
private void RunStreamingCrashTest(Action <WebSocket, TestWebSocketConnectionFeature.WebSocketChannel, WebSocketClient, CancellationTokenSource, CancellationTokenSource> induceCrash) { var logger = XUnitLogger.CreateLogger(_testOutput); var serverCts = new CancellationTokenSource(); var clientCts = new CancellationTokenSource(); using (var connection = new TestWebSocketConnectionFeature()) { var webSocket = connection.AcceptAsync().Result; var clientWebSocket = connection.Client; var bot = new StreamingTestBot((turnContext, cancellationToken) => Task.CompletedTask); var server = new CloudAdapter(new StreamingTestBotFrameworkAuthentication(), logger); var serverRunning = server.ProcessAsync(CreateWebSocketUpgradeRequest(webSocket), new Mock <HttpResponse>().Object, bot, serverCts.Token); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(h => h.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), It.IsAny <ILogger <RequestHandler> >(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .Returns(Task.FromResult(StreamingResponse.OK())); using (var client = new WebSocketClient("wss://test", clientRequestHandler.Object, logger: logger)) { var clientRunning = client.ConnectInternalAsync(clientWebSocket, clientCts.Token); var activity = new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "test", Text = "hi" }; var content = new StringContent(JsonConvert.SerializeObject(activity), Encoding.UTF8, "application/json"); var response = client.SendAsync(StreamingRequest.CreatePost("/api/messages", content)).Result; Assert.Equal(200, response.StatusCode); induceCrash(webSocket, clientWebSocket, client, serverCts, clientCts); clientRunning.Wait(); Assert.True(clientRunning.IsCompletedSuccessfully); } serverRunning.Wait(); Assert.True(serverRunning.IsCompletedSuccessfully); } }
[InlineData(false, true)] // new client, legacy server public void SimpleActivityTest(bool useLegacyClient, bool useLegacyServer) { var logger = XUnitLogger.CreateLogger(_testOutput); // Arrange var activities = new[] { new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "test", Text = "hi" } }; var verifiedResponses = activities.ToDictionary(a => a.Id, a => false); var bot = new StreamingTestBot((turnContext, cancellationToken) => { var activityClone = JsonConvert.DeserializeObject <Activity>(JsonConvert.SerializeObject(turnContext.Activity)); activityClone.Text = $"Echo: {turnContext.Activity.Text}"; return(turnContext.SendActivityAsync(activityClone, cancellationToken)); }); var server = CreateTestStreamingTransportServer(useLegacyServer, logger); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(h => h.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), It.IsAny <ILogger <RequestHandler> >(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .Returns <ReceiveRequest, ILogger <RequestHandler>, object, CancellationToken>((request, anonLogger, context, cancellationToken) => { var body = request.ReadBodyAsString(); var response = JsonConvert.DeserializeObject <Activity>(body, SerializationSettings.DefaultDeserializationSettings); Assert.NotNull(response); Assert.Equal("Echo: hi", response.Text); verifiedResponses[response.ReplyToId] = true; return(Task.FromResult(StreamingResponse.OK())); }); // Act RunActivityStreamingTest(activities, bot, server, clientRequestHandler.Object, logger, useLegacyClient); // Assert Assert.True(verifiedResponses.Values.All(verifiedResponse => verifiedResponse)); }
[InlineData(3, 100, 32, false, false)] // new client, new server public void ConcurrencyTest(int connectionCount, int messageCount, int threadCount, bool useLegacyClient, bool useLegacyServer) { var logger = XUnitLogger.CreateLogger(_testOutput); var activities = new[] { new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "test", Text = "hi", Attachments = new List <Attachment> { new Attachment { Name = @"Resources\architecture-resize.png", ContentType = "image/png", ContentUrl = $"data:image/png;base64,{Convert.ToBase64String(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png")))}", } } } }; var bot = new StreamingTestBot((turnContext, cancellationToken) => { var response = MessageFactory.Text("Echo: hi"); response.Attachments = new List <Attachment> { new Attachment { Name = @"Resources\architecture-resize.png", ContentType = "image/png", ContentUrl = $"data:image/png;base64,{Convert.ToBase64String(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png")))}", } }; return(turnContext.SendActivityAsync(response, cancellationToken)); }); var server = CreateTestStreamingTransportServer(useLegacyServer, logger); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(h => h.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), It.IsAny <ILogger <RequestHandler> >(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .Returns <ReceiveRequest, ILogger <RequestHandler>, object, CancellationToken>((request, anonLogger, context, cancellationToken) => { try { var body = request.ReadBodyAsString(); var response = JsonConvert.DeserializeObject <Activity>(body, SerializationSettings.DefaultDeserializationSettings); Assert.NotNull(response); Assert.Equal($"Echo: {activities.FirstOrDefault(a => a.Id == response.ReplyToId)?.Text}", response.Text); Assert.Equal(1, response.Attachments.Count); return(Task.FromResult(StreamingResponse.OK())); } catch (Exception e) { return(Task.FromResult(StreamingResponse.InternalServerError(new StringContent(e.ToString())))); } }); var connections = new Task[connectionCount]; for (var i = 0; i < connectionCount; i++) { connections[i] = Task.Factory.StartNew(() => RunActivityStreamingTest(activities, bot, server, clientRequestHandler.Object, logger, useLegacyClient, messageCount, threadCount)); } Task.WhenAll(connections).Wait(); }
[InlineData(false, true)] // new client, legacy server public void ActivityWithAttachmentsTest(bool useLegacyClient, bool useLegacyServer) { var logger = XUnitLogger.CreateLogger(_testOutput); // Arrange var activities = new[] { new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "test", Text = "1" }, new Activity { Id = Guid.NewGuid().ToString("N"), Type = ActivityTypes.Message, From = new ChannelAccount { Id = "testUser" }, Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("N") }, Recipient = new ChannelAccount { Id = "testBot" }, ServiceUrl = "wss://InvalidServiceUrl/api/messages", ChannelId = "test", Text = "2", Attachments = new List <Attachment> { new Attachment { Name = @"Resources\architecture-resize.png", ContentType = "image/png", ContentUrl = $"data:image/png;base64,{Convert.ToBase64String(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png")))}", } } } }; var verifiedResponses = activities.ToDictionary(a => a.Id, a => false); var bot = new StreamingTestBot((turnContext, cancellationToken) => { switch (turnContext.Activity.Text) { case "1": var response1 = MessageFactory.Text("Echo: 1"); response1.Attachments = new List <Attachment> { new Attachment { Name = @"Resources\architecture-resize.png", ContentType = "image/png", ContentUrl = $"data:image/png;base64,{Convert.ToBase64String(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, @"Resources", "architecture-resize.png")))}", } }; return(turnContext.SendActivityAsync(response1, cancellationToken)); case "2": var response2 = MessageFactory.Text("Echo: 2"); return(turnContext.SendActivityAsync(response2, cancellationToken)); default: throw new ApplicationException("Unknown Activity!"); } }); var server = CreateTestStreamingTransportServer(useLegacyServer, logger); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(h => h.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), It.IsAny <ILogger <RequestHandler> >(), It.IsAny <object>(), It.IsAny <CancellationToken>())) .Returns <ReceiveRequest, ILogger <RequestHandler>, object, CancellationToken>((request, anonLogger, context, cancellationToken) => { try { var body = request.ReadBodyAsString(); var response = JsonConvert.DeserializeObject <Activity>(body, SerializationSettings.DefaultDeserializationSettings); Assert.NotNull(response); Assert.Equal($"Echo: {activities.FirstOrDefault(a => a.Id == response.ReplyToId)?.Text}", response.Text); verifiedResponses[response.ReplyToId] = true; return(Task.FromResult(StreamingResponse.OK())); } catch (Exception e) { return(Task.FromResult(StreamingResponse.InternalServerError(new StringContent(e.ToString())))); } }); // Act RunActivityStreamingTest(activities, bot, server, clientRequestHandler.Object, logger, useLegacyClient); // Assert Assert.True(verifiedResponses.Values.All(verifiedResponse => verifiedResponse)); }
public async Task Integration_Interop_LegacyClient_MiniLoad(int threadCount, int messageCount) { var logger = XUnitLogger.CreateLogger(_outputHelper); using (var webSocketFeature = new TestWebSocketConnectionFeature()) { var botRequestHandler = new Mock <RequestHandler>(); botRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); var socket = await webSocketFeature.AcceptAsync().ConfigureAwait(false); var connection = new WebSocketStreamingConnection(socket, logger); var serverTask = Task.Run(() => connection.ListenAsync(botRequestHandler.Object)); await Task.Delay(TimeSpan.FromSeconds(1)); var clients = new List <Microsoft.Bot.Streaming.Transport.WebSockets.WebSocketClient>(); var clientRequestHandler = new Mock <RequestHandler>(); clientRequestHandler .Setup(r => r.ProcessRequestAsync(It.IsAny <ReceiveRequest>(), null, null, CancellationToken.None)) .ReturnsAsync(() => new StreamingResponse() { StatusCode = 200 }); using (var client = new Microsoft.Bot.Streaming.Transport.WebSockets.WebSocketClient( "wss://test", clientRequestHandler.Object)) { await client.ConnectInternalAsync(webSocketFeature.Client).ConfigureAwait(false); clients.Add(client); // Send request bot (server) -> channel (client) const string path = "api/version"; const string botToClientPayload = "Hello human, I'm Bender!"; Func <int, Task> testFlow = async(i) => { var request = StreamingRequest.CreatePost(path, new StringContent(botToClientPayload)); var stopwatch = Stopwatch.StartNew(); var responseFromClient = await connection.SendStreamingRequestAsync(request).ConfigureAwait(false); stopwatch.Stop(); Assert.Equal(200, responseFromClient.StatusCode); logger.LogInformation( $"Server->Client {i} latency: {stopwatch.ElapsedMilliseconds}. Status code: {responseFromClient.StatusCode}"); const string clientToBotPayload = "Hello bot, I'm Calculon!"; var clientRequest = StreamingRequest.CreatePost(path, new StringContent(clientToBotPayload)); stopwatch = Stopwatch.StartNew(); // Send request bot channel (client) -> (server) var clientToBotResult = await client.SendAsync(clientRequest).ConfigureAwait(false); stopwatch.Stop(); Assert.Equal(200, clientToBotResult.StatusCode); logger.LogInformation( $"Client->Server {i} latency: {stopwatch.ElapsedMilliseconds}. Status code: {responseFromClient.StatusCode}"); }; await testFlow(-1).ConfigureAwait(false); var tasks = new List <Task>(); using (var throttler = new SemaphoreSlim(threadCount)) { for (int j = 0; j < messageCount; j++) { await throttler.WaitAsync().ConfigureAwait(false); // using Task.Run(...) to run the lambda in its own parallel // flow on the threadpool tasks.Add( Task.Run(async() => { try { await testFlow(j).ConfigureAwait(false); } finally { throttler.Release(); } })); } await Task.WhenAll(tasks).ConfigureAwait(false); } client.Disconnect(); } await serverTask.ConfigureAwait(false); } }