Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/22.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 基于TPL数据流的网络命令处理_C#_.net_Asynchronous_Tpl Dataflow - Fatal编程技术网

C# 基于TPL数据流的网络命令处理

C# 基于TPL数据流的网络命令处理,c#,.net,asynchronous,tpl-dataflow,C#,.net,Asynchronous,Tpl Dataflow,我正在开发一个系统,该系统涉及通过TCP网络连接接受命令,然后在执行这些命令时发送响应。相当基本的东西,但我希望支持一些要求: 多个客户端可以同时连接并建立单独的会话。会话可以持续任意长或短,如果需要,同一个客户端IP可以建立多个并行会话 每个会话可以同时处理多个命令,因为某些请求的操作可以并行执行 我希望使用async/await干净地实现这一点,根据我所读到的内容,TPL数据流听起来是一种很好的方法,可以干净地将处理分解为可以在线程池上运行的良好块,而不是为不同的会话/命令捆绑线程,阻塞等待

我正在开发一个系统,该系统涉及通过TCP网络连接接受命令,然后在执行这些命令时发送响应。相当基本的东西,但我希望支持一些要求:

  • 多个客户端可以同时连接并建立单独的会话。会话可以持续任意长或短,如果需要,同一个客户端IP可以建立多个并行会话
  • 每个会话可以同时处理多个命令,因为某些请求的操作可以并行执行
  • 我希望使用async/await干净地实现这一点,根据我所读到的内容,TPL数据流听起来是一种很好的方法,可以干净地将处理分解为可以在线程池上运行的良好块,而不是为不同的会话/命令捆绑线程,阻塞等待句柄

    这就是我开始的部分(为了简化而去掉了一些部分,例如异常处理的细节;我还省略了一个包装器,它为网络I/O提供了一个高效的等待程序):

    private readonly任务\u serviceTask;
    私有只读任务_commandsTask;
    私有只读取消令牌源\u取消;
    专用只读缓冲块_pending命令;
    公用网络服务(ICommandProcessor commandProcessor)
    {
    _commandProcessor=commandProcessor;
    IsRunning=true;
    _取消=新的CancellationTokenSource();
    _pendingCommands=新的缓冲块();
    _serviceTask=Task.Run((Func)RunService);
    _commandsTask=Task.Run((Func)RunCommands);
    }
    公共bool正在运行{get;private set;}
    专用异步任务RunService()
    {
    _侦听器=新的TcpListener(IPAddress.Any,ServicePort);
    _listener.Start();
    同时(正在运行)
    {
    socketclient=null;
    尝试
    {
    client=wait_listener.AcceptSocketAsync();
    client.Blocking=false;
    var session=RunSession(客户端);
    锁定(_会话)
    {
    _添加(会话);
    }
    }
    捕获(例外情况除外)
    {//在这里处理。。。
    }
    }
    }
    专用异步任务RunCommands()
    {
    同时(正在运行)
    {
    var command=wait _pendingCommands.ReceiveAsync(_cancellation.Token);
    var task=task.Run(()=>RunCommand(command));
    }
    }
    专用异步任务RunCommand(Command)
    {
    尝试
    {
    var response=await_commandProcessor.RunCommand(command.Content);
    发送(command.Client,response);
    }
    捕获(例外情况除外)
    {
    //在这里处理常规命令异常。。。
    }
    }
    专用异步任务运行会话(套接字客户端)
    {
    while(client.Connected)
    {
    变量读取器=新的分隔命令读取器(客户端);
    尝试
    {
    var content=await reader.ReceiveCommand();
    _Post(新命令(客户端,内容));
    }
    捕获(例外情况除外)
    {
    //这里的异常处理。。。
    }
    }
    }
    
    基本原理看起来很简单,但有一部分让我大吃一惊:如何确保在关闭应用程序时,我等待所有挂起的命令任务完成?当我使用Task.Run来执行命令时,我得到了Task对象,但是如何跟踪挂起的命令,以便在允许服务关闭之前确保所有命令都已完成

    我曾考虑过使用一个简单的列表,在命令完成后从列表中删除命令,但我想知道我是否缺少一些TPL数据流中的基本工具,这些工具可以让我更干净地完成这项工作


    编辑:


    在阅读更多关于TPL数据流的内容时,我想知道我应该使用的是一个增加了MaxDegreeOfParallelism以允许处理并行命令的方法吗?这为可以并行运行的命令数量设置了上限,但我认为这对我的系统来说是一个合理的限制。我很想从那些有TPL数据流经验的人那里了解我是否走上了正确的道路。

    任务默认为后台任务,这意味着当应用程序终止时,它们也会立即终止。您应该使用线程而不是任务。然后您可以设置:

    Thread.IsBackground = false;
    
    这将防止应用程序在工作线程运行时终止。 当然,这需要对上述代码进行一些更改

    更重要的是,在执行shutdown方法时,您还可以等待来自主线程的任何未完成任务


    我看不到更好的解决办法

    是的,所以。。。你有点在使用第三方物流的力量。如果您订阅的是TPL数据流样式,那么在后台
    任务中的while循环中仍然手动从
    BufferBlock
    接收项目这一事实并不是您想要的“方式”

    您要做的是将一个
    ActionBlock
    链接到
    BufferBlock
    ,并在其中进行命令处理/发送。在这个块中,您可以设置
    MaxDegreeOfParallelism
    ,以控制要处理的并发命令的数量。因此,设置可能如下所示:

    // Initialization logic to build up the TPL flow
    _pendingCommands = new BufferBlock<Command>();
    _commandProcessor = new ActionBlock<Command>(this.ProcessCommand);
    
    _pendingCommands.LinkTo(_commandProcessor);
    
    private Task ProcessCommand(Command command)
    {
       var response = await _commandProcessor.RunCommand(command.Content);
       this.Send(command.Client, response);
    }
    
    如果你想获得额外积分,你甚至可以将命令处理和命令发送分开。这将允许您分别配置这些步骤。例如,您可能需要限制处理命令的线程数量,但希望有更多线程发送响应。您只需在流的中间引入一个
    TransformBlock
    即可:

    _pendingCommands = new BufferBlock<Command>();
    _commandProcessor = new TransformBlock<Command, Tuple<Client, Response>>(this.ProcessCommand);
    _commandSender = new ActionBlock<Tuple<Client, Response>(this.SendResponseToClient));
    
    _pendingCommands.LinkTo(_commandProcessor);
    _commandProcessor.LinkTo(_commandSender);
    
    private Task ProcessCommand(Command command)
    {
       var response = await _commandProcessor.RunCommand(command.Content);
    
       return Tuple.Create(command, response);
    }
    
    private Task SendResponseToClient(Tuple<Client, Response> clientAndResponse)
    {
       this.Send(clientAndResponse.Item1, clientAndResponse.Item2);
    }
    
    _pendingCommands=new B
    
    _pendingCommands.Complete();
    _commandProcessor.Completion.Wait();
    
    _pendingCommands = new BufferBlock<Command>();
    _commandProcessor = new TransformBlock<Command, Tuple<Client, Response>>(this.ProcessCommand);
    _commandSender = new ActionBlock<Tuple<Client, Response>(this.SendResponseToClient));
    
    _pendingCommands.LinkTo(_commandProcessor);
    _commandProcessor.LinkTo(_commandSender);
    
    private Task ProcessCommand(Command command)
    {
       var response = await _commandProcessor.RunCommand(command.Content);
    
       return Tuple.Create(command, response);
    }
    
    private Task SendResponseToClient(Tuple<Client, Response> clientAndResponse)
    {
       this.Send(clientAndResponse.Item1, clientAndResponse.Item2);
    }