c# - Obtenga salida en vivo del proceso
redirect process (5)
Tengo un problema en mi proyecto Me gustaría lanzar un proceso, 7z.exe (versión de consola). Probé tres cosas diferentes:
- Process.StandardOutput.ReadToEnd ();
- OutputDataReceived y BeginOutputReadLine
- StreamWriter
Nada funciona. Siempre "espera" que el final del proceso muestre lo que quiero. No tengo ningún código para poner, solo si quieres mi código con una de las cosas listadas arriba. Gracias.
Editar: Mi código:
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
this.sr = process.StandardOutput;
while (!sr.EndOfStream)
{
String s = sr.ReadLine();
if (s != "")
{
System.Console.WriteLine(DateTime.Now + " - " + s);
}
}
O
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += new DataReceivedEventHandler(recieve);
process.StartInfo.CreateNoWindow = true;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
public void recieve(object e, DataReceivedEventArgs outLine)
{
System.Console.WriteLine(DateTime.Now + " - " + outLine.Data);
}
O
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = p.StandardOutput.ReadToEnd();
process.WaitForExit();
Donde "proceso" es mi proceso prefabricado
Ok, sé por qué no funciona correctamente: 7z.exe es el error: muestra una carga porcentual en la consola y envía información solo cuando el archivo actual ha finalizado. En la extracción, por ejemplo, funciona bien :). Buscaré otra forma de usar funciones 7z sin 7z.exe (tal vez con 7za.exe o con alguna DLL). Gracias a todos. Para responder a la pregunta, ¡el evento OuputDataRecibved funciona bien!
He utilizado la clase CmdProcessor descrita aquí en varios proyectos con mucho éxito. Parece un poco desalentador al principio, pero es muy fácil de usar.
Prueba esto.
Process notePad = new Process();
notePad.StartInfo.FileName = "7z.exe";
notePad.StartInfo.RedirectStandardOutput = true;
notePad.StartInfo.UseShellExecute = false;
notePad.Start();
StreamReader s = notePad.StandardOutput;
String output= s.ReadToEnd();
notePad.WaitForExit();
Deja que lo anterior esté en un thread
.
Ahora, para actualizar la salida a la interfaz de usuario, puede usar un timer
con dos líneas
Console.Clear();
Console.WriteLine(output);
Esto puede ayudarte
Eche un vistazo a esta página, parece que esta es la solución para usted: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx y http://msdn.microsoft. com / en-us / library / system.diagnostics.process.standardoutput.aspx
[Editar] Este es un ejemplo de trabajo:
Process p = new Process();
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = @"C:/Program Files (x86)/gnuwin32/bin/ls.exe";
p.StartInfo.Arguments = "-R C://";
p.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
Console.WriteLine(e.Data);
}
);
p.ErrorDataReceived += new DataReceivedEventHandler((s, e) => { Console.WriteLine(e.Data); });
p.Start();
p.BeginOutputReadLine();
Por cierto, ls -RC: / enumera todos los archivos desde la raíz de C: recursivamente. Estos son muchos archivos, y estoy seguro de que no se hará cuando aparezcan los primeros resultados en la pantalla. Existe la posibilidad de que 7zip contenga la salida antes de mostrarla. No estoy seguro de qué parámetros le das al proceso.
No sé si alguien todavía está buscando una solución para esto, pero me ha venido varias veces porque estoy escribiendo una herramienta en Unity para algunos juegos y debido a la limitada interoperabilidad de ciertos sistemas con mono (como PIA para leer texto de Word, por ejemplo), a menudo tengo que escribir ejecutables específicos del sistema operativo (a veces Windows, a veces MacOS) y ejecutarlos desde Process.Start ().
El problema es que cuando ejecutas un ejecutable como este, se activará en otro hilo que bloquea tu aplicación principal, causando un bloqueo. Si desea proporcionar comentarios útiles a sus usuarios durante este tiempo más allá de los íconos giratorios evocados por su sistema operativo respectivo, entonces está medio enredado. El uso de una transmisión no funcionará porque el hilo aún está bloqueado hasta que finalice la ejecución.
La solución a la que he llegado, que puede parecer extrema para algunas personas, pero a mí me parece que funciona bastante bien, es usar sockets y multiprocesamiento para configurar comunicaciones síncronas confiables entre las dos aplicaciones. Por supuesto, esto solo funciona si estás creando ambas aplicaciones. Si no, creo que no tienes suerte. ... Me gustaría ver si funciona solo con multihebra usando un enfoque de transmisión tradicional, por lo que si alguien quisiera probar eso y publicar los resultados aquí, sería genial.
De todos modos, aquí está la solución que actualmente funciona para mí:
En la aplicación principal o de llamada, hago algo como esto:
/// <summary>
/// Handles the OK button click.
/// </summary>
private void HandleOKButtonClick() {
string executableFolder = "";
#if UNITY_EDITOR
executableFolder = Path.Combine(Application.dataPath, "../../../../build/Include/Executables");
#else
executableFolder = Path.Combine(Application.dataPath, "Include/Executables");
#endif
EstablishSocketServer();
var proc = new Process {
StartInfo = new ProcessStartInfo {
FileName = Path.Combine(executableFolder, "WordConverter.exe"),
Arguments = locationField.value + " " + _ipAddress.ToString() + " " + SOCKET_PORT.ToString(),
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
Aquí es donde establezco el servidor de socket:
/// <summary>
/// Establishes a socket server for communication with each chapter build script so we can get progress updates.
/// </summary>
private void EstablishSocketServer() {
//_dialog.SetMessage("Establishing socket connection for updates. /n");
TearDownSocketServer();
Thread currentThread;
_ipAddress = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0];
_listener = new TcpListener(_ipAddress, SOCKET_PORT);
_listener.Start();
UnityEngine.Debug.Log("Server mounted, listening to port " + SOCKET_PORT);
_builderCommThreads = new List<Thread>();
for (int i = 0; i < 1; i++) {
currentThread = new Thread(new ThreadStart(HandleIncomingSocketMessage));
_builderCommThreads.Add(currentThread);
currentThread.Start();
}
}
/// <summary>
/// Tears down socket server.
/// </summary>
private void TearDownSocketServer() {
_builderCommThreads = null;
_ipAddress = null;
_listener = null;
}
Aquí está mi controlador de socket para el hilo ... tenga en cuenta que tendrá que crear varios hilos en algunos casos; es por eso que tengo esa lista _builderCommThreads allí (la porté desde el código en otro lugar donde estaba haciendo algo similar pero llamando a varias instancias en una fila):
/// <summary>
/// Handles the incoming socket message.
/// </summary>
private void HandleIncomingSocketMessage() {
if (_listener == null) return;
while (true) {
Socket soc = _listener.AcceptSocket();
//soc.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 10000);
NetworkStream s = null;
StreamReader sr = null;
StreamWriter sw = null;
bool reading = true;
if (soc == null) break;
UnityEngine.Debug.Log("Connected: " + soc.RemoteEndPoint);
try {
s = new NetworkStream(soc);
sr = new StreamReader(s, Encoding.Unicode);
sw = new StreamWriter(s, Encoding.Unicode);
sw.AutoFlush = true; // enable automatic flushing
while (reading == true) {
string line = sr.ReadLine();
if (line != null) {
//UnityEngine.Debug.Log("SOCKET MESSAGE: " + line);
UnityEngine.Debug.Log(line);
lock (_threadLock) {
// Do stuff with your messages here
}
}
}
//
} catch (Exception e) {
if (s != null) s.Close();
if (soc != null) soc.Close();
UnityEngine.Debug.Log(e.Message);
//return;
} finally {
//
if (s != null) s.Close();
if (soc != null) soc.Close();
UnityEngine.Debug.Log("Disconnected: " + soc.RemoteEndPoint);
}
}
return;
}
Por supuesto, deberás declarar algunas cosas en la parte superior:
private TcpListener _listener = null;
private IPAddress _ipAddress = null;
private List<Thread> _builderCommThreads = null;
private System.Object _threadLock = new System.Object();
... luego en el ejecutable invocado, configure el otro extremo (utilicé estática en este caso, puede usar lo que quiera):
private static TcpClient _client = null;
private static Stream _s = null;
private static StreamReader _sr = null;
private static StreamWriter _sw = null;
private static string _ipAddress = "";
private static int _port = 0;
private static System.Object _threadLock = new System.Object();
/// <summary>
/// Main method.
/// </summary>
/// <param name="args"></param>
static void Main(string[] args) {
try {
if (args.Length == 3) {
_ipAddress = args[1];
_port = Convert.ToInt32(args[2]);
EstablishSocketClient();
}
// Do stuff here
if (args.Length == 3) Cleanup();
} catch (Exception exception) {
// Handle stuff here
if (args.Length == 3) Cleanup();
}
}
/// <summary>
/// Establishes the socket client.
/// </summary>
private static void EstablishSocketClient() {
_client = new TcpClient(_ipAddress, _port);
try {
_s = _client.GetStream();
_sr = new StreamReader(_s, Encoding.Unicode);
_sw = new StreamWriter(_s, Encoding.Unicode);
_sw.AutoFlush = true;
} catch (Exception e) {
Cleanup();
}
}
/// <summary>
/// Clean up this instance.
/// </summary>
private static void Cleanup() {
_s.Close();
_client.Close();
_client = null;
_s = null;
_sr = null;
_sw = null;
}
/// <summary>
/// Logs a message for output.
/// </summary>
/// <param name="message"></param>
private static void Log(string message) {
if (_sw != null) {
_sw.WriteLine(message);
} else {
Console.Out.WriteLine(message);
}
}
... Estoy usando esto para lanzar una herramienta de línea de comando en Windows que usa el material de PIA para extraer texto de un documento de Word. Probé PIA los .dlls en Unity, pero encontré problemas de interoperabilidad con mono. También lo estoy usando en MacOS para invocar scripts de shell que inician instancias adicionales de Unity en modo batch y ejecutar scripts de editor en aquellas instancias que responden a la herramienta a través de esta conexión de socket. Es genial, porque ahora puedo enviar comentarios al usuario, depurar, monitorear y responder a pasos específicos en el proceso, etcétera, etcétera.
HTH
Para gestionar correctamente la salida y / o la redirección de errores, también debe redirigir la entrada. Parece ser una característica / error en el tiempo de ejecución de la aplicación externa que está comenzando y de lo que he visto hasta ahora, no se menciona en ningún otro lado.
Ejemplo de uso:
Process p = new Process(...);
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true; // Is a MUST!
p.EnableRaisingEvents = true;
p.OutputDataReceived += OutputDataReceived;
p.ErrorDataReceived += ErrorDataReceived;
Process.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();
p.WaitForExit();
p.OutputDataReceived -= OutputDataReceived;
p.ErrorDataReceived -= ErrorDataReceived;
...
void OutputDataReceived(object sender, DataReceivedEventArgs e)
{
// Process line provided in e.Data
}
void ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
// Process line provided in e.Data
}