首先看python项目代码:
import time
import argparse
# cxfreeze test.py --target-dir dist
def main():
parser = argparse.ArgumentParser()
parser.add_argument("total_count", type=int)
parser.add_argument("time_span", type=int)
args = parser.parse_args()
count = 0
for i in range(0, args.total_count):
time.sleep(args.time_span)
count = count + 1
print(f'count now :{count}', end='\n', flush=True)
if __name__ == "__main__":
main()
这段代码就是根据总计数和时间间隔来循环输出测试信息。
由于要实时的读取信息,所以必须采用异步编程的方式
如何异步读取exe的输出请参考这篇文章:【.net 深呼吸】启动一个进程并实时获取状态信息 - 东邪独孤 - 博客园 (cnblogs.com)
后台调取CMD的类:
//设置CMD命令及其参数
public CMD(string path)
{
process = new Process();
startInfo = new ProcessStartInfo();
startInfo.FileName = "cmd.exe";
//设置工作的目录
startInfo.WorkingDirectory = path;
// 不使用操作系统PowerShell启动进程
startInfo.UseShellExecute = false;
// 重定向标准输入流
startInfo.RedirectStandardInput = true;
// 重定向标准输出流
startInfo.RedirectStandardOutput = true;
// 重定向错误输出流
startInfo.RedirectStandardError = true;
// 不创建新窗口
startInfo.CreateNoWindow = true;
//隐藏窗口
//startInfo.WindowStyle = ProcessWindowStyle.Hidden;
}
同步执行命令:
// 执行命令
public string CMD_Run_Sync(string command)
{
//定义返回信息
string cmd_putout = null;
//获取参数对象
process.StartInfo = startInfo;
//启动
process.Start();
// 写入CMD命令到标准输入流
process.StandardInput.WriteLine(command);
process.StandardInput.WriteLine("exit");
// 读取CMD的输出
string output = process.StandardOutput.ReadToEnd();
cmd_putout = output;
string error = process.StandardError.ReadToEnd();
// 输出错误信息
if (!string.IsNullOrEmpty(error))
{
cmd_putout = error;
}
return cmd_putout;
// 等待进程退出
//process.WaitForExit();
}
异步执行命令:
public void CMD_Run_Async(string command)
{
process.EnableRaisingEvents = true;
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
if(output_information != e.Data)
{
output_information = e.Data;
OnDataSend(output_information);
}
Console.WriteLine(e.Data);
}
};
//获取参数对象
process.StartInfo = startInfo;
// 启动进程并开始异步读取输出
process.Start();
process.StandardInput.WriteLine(command);
process.StandardInput.WriteLine("exit");
process.BeginOutputReadLine();
}
在WPF前台调用的代码:
private async void Button4_Click(object sender, RoutedEventArgs e)
{
CMD cmdTransform;
DirectoryInfo di = new DirectoryInfo(Environment.CurrentDirectory);
string cmd_start_path = di.FullName;
//开始异步执行任务
await Task.Run(() =>
{
cmd_start_path = cmd_start_path + "\\tool\\count";
string command = "test.exe 5 1";
cmdTransform = new CMD(cmd_start_path);
cmdTransform.DataSend += ReceiveData;
cmdTransform.CMD_Run_Async(command);
});
}
上述都是修改后的代码,应该没问题
修改前,当我通过C#调用python生成的exe时,发现并不是逐行读取,而是一次性读取,此时我已经改为异步执行CMD命令的代码了。我想到之前的参考文章中说明,一般都是先写入缓冲区,然后当缓冲区满了或者进程结束才会输出到标准流中,由于参考文献中使用的是C#编写的测试输出有
StreamWriter writer
writer.Flush()
可以强制刷新,那么python中的print是不是也是这样呢?根据文心一言回答,print的强制刷新需要添加print(f'count now :{count}', end='\n', flush=True)。这样每次调用print的时候会以换行符结束,然后强制刷新到标准流。
这时候将修改过的python打包,再去C#调用时,发现还是一次性读取。解决方法还是在那篇参考文章中,需要加上process.EnableRaisingEvents = true;否则 process.OutputDataReceived事件根本不会触发。
加上之后,再去调用,发现在Debug模式下,可以正常一行行输出,但是Release模式下却会创建一个新的窗口,然后在新窗口中逐行输出
我以为是使用cx_Freeze打包的问题,于是我在setup.py脚本文件中添加了参数base="Win32GUI"
from cx_Freeze import setup, Executable
# 打包配置
build_exe_options = {
"packages": [],
"excludes": [],
"include_files": [],
"include_msvcr": True
}
executables = [Executable("test.py", base="Win32GUI", icon="app.ico")]
setup(
name="AppName",
version="1.0",
description="描述你的应用",
options={"build_exe": build_exe_options},
executables=executables
)
调用还是会创建新窗口,改为base="None",一样会。
突然想到在调用的时候,系统弹出消息框,是否执行此exe,反应过来原来是权限问题,所以要以管理员身份启动VS,这时候再去调用就没问题了。
在此基础上,添加委托和事件,让exe的输出从控制台传到主线程UI中,效果如下: