Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/unix/3.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# 如何使用2个数据源获得异步流返回_C#_Asynchronous_Tpl Dataflow_Iasyncenumerable - Fatal编程技术网

C# 如何使用2个数据源获得异步流返回

C# 如何使用2个数据源获得异步流返回,c#,asynchronous,tpl-dataflow,iasyncenumerable,C#,Asynchronous,Tpl Dataflow,Iasyncenumerable,我有一个函数,它以异步流的形式返回标准输出数据,该数据是运行System.Diagnostics.Process得到的。当前方法中的所有内容都按预期工作;我可以在await foreach()循环中调用它,并获得由外部exe生成的每一行输出 private static async IAsyncEnumerable<string> ProcessAsyncStream ( ProcessStartInfo processStartInfo) { // Ensure th

我有一个函数,它以异步流的形式返回标准输出数据,该数据是运行
System.Diagnostics.Process
得到的。当前方法中的所有内容都按预期工作;我可以在
await foreach()
循环中调用它,并获得由外部exe生成的每一行输出

private static async IAsyncEnumerable<string> ProcessAsyncStream (
    ProcessStartInfo processStartInfo)
{
   // Ensure that process is destroyed when this method exits
   using var process = new Process() { StartInfo = processStartInfo };

   // Buffer used to pass data from event-handler back to this method
   BufferBlock<string> dataBuffer = new BufferBlock<string>();

   process.OutputDataReceived += (s, e) =>
   {
      if (e.Data is null)
      {
         dataBuffer.Complete();
      }
      else
      {
         dataBuffer.Post(e.Data);
      }
   };

   // Start process and redirect output streams
   process.Start();
   process.BeginOutputReadLine();

   // Return data line by line  
   while (await dataBuffer.OutputAvailableAsync())
      yield return dataBuffer.Receive();
}
并将
ProcessAsyncStream()
更改为如下所示

private static async IAsyncEnumerable<ProcessData> ProcessAsyncStream (
    ProcessStartInfo processStartInfo)
{
   // Ensure that process is destroyed when this method exits
   using var process = new Process() { StartInfo = processStartInfo };

   // Buffer used to pass data from event-handlers back to this method
   BufferBlock<string> outputDataBuffer = new BufferBlock<string>();
   BufferBlock<string> errorDataBuffer = new BufferBlock<string>();

   
   process.OutputDataReceived += (s, e) =>
   {
      if (e.Data is null)
      {
         outputDataBuffer.Complete();
      }
      else
      {
         outputDataBuffer.Post(e.Data);
      }
   };

   process.ErrorDataReceived += (s, e) =>
   {
      if (e.Data is null)
      {
         errorDataBuffer.Complete();
      }
      else
      {
         errorDataBuffer.Post(e.Data);
      }
   };

   // Start process and redirect output streams
   process.Start();
   process.BeginOutputReadLine();

   // Return data line by line
   while (await outputDataBuffer.OutputAvailableAsync()
          || await errorDataBuffer.OutputAvailableAsync())
      yield return new ProcessData() 
      {
         Error = errorDataBuffer.Receive(), 
         Output = outputDataBuffer.Receive()
      }
}
私有静态异步IAsyncEnumerable ProcessAsyncStream(
ProcessStartInfo ProcessStartInfo)
{
//确保此方法退出时销毁进程
使用var process=new process(){StartInfo=processStartInfo};
//用于将数据从事件处理程序传递回此方法的缓冲区
BufferBlock outputDataBuffer=新的BufferBlock();
BufferBlock errorDataBuffer=新的BufferBlock();
process.OutputDataReceived+=(s,e)=>
{
如果(例如,数据为空)
{
outputDataBuffer.Complete();
}
其他的
{
outputDataBuffer.Post(即数据);
}
};
process.ErrorDataReceived+=(s,e)=>
{
如果(例如,数据为空)
{
errorDataBuffer.Complete();
}
其他的
{
errorDataBuffer.Post(e.Data);
}
};
//启动进程并重定向输出流
process.Start();
process.BeginOutputReadLine();
//逐行返回数据
while(等待outputDataBuffer.OutputAvailableAsync()
||等待errorDataBuffer.OutputAvailableAsync())
产生返回新的ProcessData()
{
Error=errorDataBuffer.Receive(),
Output=outputDataBuffer.Receive()
}
}
问题是,如果任何一个缓冲区在另一个方法挂起之前完成,则另一个方法挂起,因为该缓冲区的
.Receive()
没有任何数据要接收。如果我将
while
条件更改为
和&
,则不会从其他缓冲区获取所有数据


有什么建议吗?

您可以使用
ProcessData
项的单个缓冲区:

var buffer = new BufferBlock<ProcessData>();
下面是
Complete
方法的实现:

bool[] completeState = new bool[2];
void Complete(int index)
{
    bool completed;
    lock (completeState.SyncRoot)
    {
        completeState[index - 1] = true;
        completed = completeState.All(v => v);
    }
    if (completed) buffer.Complete();
}

关于实际问题,读取块的处理流程存在问题。最简单的解决方案是只使用一个缓冲区,将多个生产者和一个消费者与一个消息包结合使用

您试图用数据流块解决的概念性问题是异步流事件的基本性质。推送事件,并拉取异步流

有几种解决方案可以将它们映射到一起,尽管我认为最优雅的方法是使用无界通道作为缓冲区

通道比数据流更现代,自由度更小,比
缓冲块
更笨重,而且非常轻量级和高度优化。此外,我只需要为不同的响应类型传递一个包装器

忽略任何其他问题(概念或其他)

给定的

public enum MessageType
{
   Output,
   Error
}

public class Message
{
   public MessageType MessageType { get; set; }
   public string Data { get; set; }

   public Message(string data, MessageType messageType )
   {
      Data = data;
      MessageType = messageType;
   }
}
用法

private async IAsyncEnumerable<Message> ProcessAsyncStreamAsync(
     ProcessStartInfo processStartInfo, 
     CancellationToken cancellationToken)
{
   using var process = new Process() { StartInfo = processStartInfo };

   var ch = Channel.CreateUnbounded<Message>();
   var completeCount = 0;

   void OnReceived(string data, MessageType type)
   {
      // The Interlocked memory barrier is likely overkill here
      if (data is null && Interlocked.Increment(ref completeCount) == 2)
         ch?.Writer.Complete();
      else
         ch?.Writer.WriteAsync(new Message(data, type), cancellationToken);
   }

   process.OutputDataReceived += (_, args) => OnReceived(args.Data, MessageType.Output);
   process.ErrorDataReceived += (_, args) => OnReceived(args.Data, MessageType.Error);

   // start the process 
   // ...

   await foreach (var message in ch.Reader
           .ReadAllAsync(cancellationToken)
           .ConfigureAwait(false))
      yield return message;

   // cleanup
   // ...
}
专用异步IAsyncEnumerable ProcessAsyncStreamAsync(
ProcessStartInfo ProcessStartInfo,
取消令牌(取消令牌)
{
使用var process=new process(){StartInfo=processStartInfo};
var ch=Channel.CreateUnbounded();
var completeCount=0;
void OnReceived(字符串数据,消息类型)
{
//在这里,互锁的内存屏障很可能是杀伤力过大
如果(数据为空&&Interlocked.Increment(ref completeCount)==2)
ch?.Writer.Complete();
其他的
ch?.Writer.WriteAsync(新消息(数据、类型)、cancellationToken);
}
process.OutputDataReceived+=(\ux,args)=>OnReceived(args.Data,MessageType.Output);
process.ErrorDataReceived+=(\ux,args)=>OnReceived(args.Data,MessageType.Error);
//开始这个过程
// ...
等待foreach(通道读取器中的var消息
.ReadAllAsync(取消令牌)
.ConfigureWait(错误))
产生返回消息;
//清理
// ...
}

注意:完全未测试

在退出时完成

void HandleData(object sender, DataReceivedEventArgs e)
{
    if (e.Data != null) dataBuffer.Post(e.Data);
}

process.OutputDataReceived += HandleData;
process.ErrorDataReceived += HandleData;
process.Exited += (s,e) => 
{
    process.WaitForExit();
    dataBuffer.Complete();
};

是否将
Error=errorDataBuffer.OutputAvailableAsync()?errorDataBuffer.Receive():null(对于输出也是一样)对您有效吗?您在上次
while
循环中检查了
outputDataBuffer.OutputAvailableAsync()
两次。这是故意的还是错误?这是一个奇怪的解决方案。您是否也应该使用
TryReceive
master_ruko不,使用
TryReceive
方法是安全的。在多消费者场景中,如果在等待
OutputAvailableAsync
后调用
Receive
,则可能会获得
invalidoOperationException
。另外,通过在
while
循环中调用
TryReceive
,您可能会在高吞吐量场景中获得更好的性能,因为
OutputAvailableAsync
相对昂贵。另外,如果您对性能感兴趣,可以认为它优于
BufferBlock
。他们在内部使用
ValueTask
s,因此分配更少。传播
struct ProcessData
而不是
类也可能是有益的。虽然我确信
Process
总是按照预期的顺序引发事件,但我认为没有理由期望Windows线程调度程序保证
Exited
事件总是在最后一次
HandleData()之后引发
已拨打电话。这些事件是在线程池中异步引发的,并且彼此不同步,因此在引发
退出
事件后,总是有可能再次调用
HandleData()
。换句话说,我明白为什么这种方法很诱人,但它似乎是最糟糕的选择之一(甚至不包括OP保持stdout和stderr dat
private async IAsyncEnumerable<Message> ProcessAsyncStreamAsync(
     ProcessStartInfo processStartInfo, 
     CancellationToken cancellationToken)
{
   using var process = new Process() { StartInfo = processStartInfo };

   var ch = Channel.CreateUnbounded<Message>();
   var completeCount = 0;

   void OnReceived(string data, MessageType type)
   {
      // The Interlocked memory barrier is likely overkill here
      if (data is null && Interlocked.Increment(ref completeCount) == 2)
         ch?.Writer.Complete();
      else
         ch?.Writer.WriteAsync(new Message(data, type), cancellationToken);
   }

   process.OutputDataReceived += (_, args) => OnReceived(args.Data, MessageType.Output);
   process.ErrorDataReceived += (_, args) => OnReceived(args.Data, MessageType.Error);

   // start the process 
   // ...

   await foreach (var message in ch.Reader
           .ReadAllAsync(cancellationToken)
           .ConfigureAwait(false))
      yield return message;

   // cleanup
   // ...
}
void HandleData(object sender, DataReceivedEventArgs e)
{
    if (e.Data != null) dataBuffer.Post(e.Data);
}

process.OutputDataReceived += HandleData;
process.ErrorDataReceived += HandleData;
process.Exited += (s,e) => 
{
    process.WaitForExit();
    dataBuffer.Complete();
};