运行Specflow测试出现InvalidOperationException进程流混合同步异步操作报错
问题原因
- 核心报错原因是对同一个进程输出流同时使用了同步和异步两种读取方式:在
ExecuteShellCommand方法启动进程后,先通过process.StandardOutput.ReadToEnd()同步读取输出,后续又调用process.BeginOutputReadLine()开启异步读取,.NET不允许对进程流混用两种操作模式,直接触发了InvalidOperationException。 - 次要问题是同步方法中调用异步方法时使用
.Result阻塞,存在线程死锁风险,且Specflow原生支持异步步骤方法,不需要强行用同步阻塞的方式拿异步返回结果。 - 冗余代码问题:重复注册了
OutputDataReceived和ErrorDataReceived事件处理,多余注册的OutputHandler可能和原有匿名委托逻辑冲突。
修复步骤
- 清理
ExecuteShellCommand方法中的冗余逻辑和同步读代码
删除try块中的以下代码:
// 重复注册的事件,删掉 process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler); process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler); // 同步读输出的代码,删掉,和后续异步读冲突 StreamReader reader = process.StandardOutput; string output = reader.ReadToEnd(); result.Output = output;
- 完善输出赋值逻辑
原来的代码只有进程退出码非0时才给result.Output赋值,正常退出时也要补全赋值,修改对应分支代码:
if (await Task.WhenAny(Task.Delay(timeout), processTask) == processTask && waitForExit.Result) { result.Completed = true; result.ExitCode = process.ExitCode; // 不管退出码是否正常都赋值输出 result.Output = $"{outputBuilder.ToString()}{Environment.NewLine}{errorBuilder.ToString()}"; }
- 修改Specflow步骤方法为异步实现
将步骤方法的返回值从void改为async Task,调用异步方法时用await代替.Result阻塞:
[Given(@"messages are streaming from ""(.*)"" to topic ""(.*)"" on partition (.*)")] public async Task GivenMessagesAreStreamingFromToTopicOnPartition(string binaryFile, string topic, int partition) { var MessageInjectorOptions = new MessageInjectorOptions(); _configuration.GetSection("JavaMessageInjector").Bind(MessageInjectorOptions); _logger.LogInformation("MessageInjector Command: " + MessageInjectorOptions.MessageInjectorFilename + " " + MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition); _output.WriteLine("Console MessageInjector Command: " + MessageInjectorOptions.MessageInjectorFilename + " " + MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition); // 用await代替.Result拿结果 var result = await TestHelper.ExecuteShellCommand(MessageInjectorOptions.MessageInjectorFilename, MessageInjectorOptions.MessageInjectorParameters + " " + binaryFile + " " + topic + " " + partition, 300000, true); _logger.LogInformation("Output: " + result.Output); _logger.LogInformation("MessageInjector Exit Code: " + result.ExitCode + ", Completed: " + result.Completed + " Output: " + result.Output); }
内容的提问来源于stack exchange,提问作者runnerpaul
相关产品推荐
相关产品推荐

