web-dev-qa-db-fra.com

Obtenir la sortie en direct du processus

J'ai un problème dans mon projet. Je voudrais lancer un processus, 7z.exe (version console). J'ai essayé trois choses différentes:

  • Process.StandardOutput.ReadToEnd ();
  • OutputDataReceived & BeginOutputReadLine
  • StreamWriter

Rien ne fonctionne. Il faut toujours "attendre" la fin du processus pour montrer ce que je veux. Je n'ai pas de code à mettre, juste si vous voulez mon code avec l'un des éléments énumérés ci-dessus. Merci.

Edit: Mon code:

        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);
            }
        }

Ou

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);
}

Ou

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
string output = p.StandardOutput.ReadToEnd();
process.WaitForExit();

Où "processus" est mon processus pré-fabriqué

Ok, je sais pourquoi cela ne fonctionne pas correctement: 7z.exe est le bogue: il affiche un pourcentage de chargement dans la console et envoie des informations uniquement lorsque le fichier en cours est terminé. Dans l'extraction par exemple, ça fonctionne très bien :). Je vais rechercher un autre moyen d'utiliser les fonctions 7z sans 7z.exe (peut-être avec 7za.exe ou avec des DLL). Merci à tous. Pour répondre à la question, l'événement OuputDataRecieved fonctionne correctement!

18
Extaze

Jetez un coup d’œil à cette page, c’est la solution pour vous: http://msdn.Microsoft.com/en-us/library/system.diagnostics.process.beginoutputreadline.aspx et http: //msdn.Microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx

[Modifier] Voici un exemple de travail:

        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();
        p.BeginErrorReadLine();

Btw, ls -R C:\répertorie tous les fichiers de la racine de C: récursivement. Ce sont beaucoup de fichiers, et je suis sûr que cela n’est pas fait lorsque les premiers résultats apparaissent à l’écran. Il est possible que 7Zip conserve la sortie avant de la montrer. Je ne sais pas quels paramètres vous donnez au processus.

19

Pour gérer correctement la redirection des sorties et/ou des erreurs, vous devez également rediriger les entrées. Il semble qu’il s’agisse d’une fonctionnalité/bogue au moment de l’exécution de l’application externe que vous démarrez et de ce que j’ai vu jusqu’à présent, cela n’est mentionné nulle part ailleurs. .

Exemple d'utilisation:

        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
    }
5
user3042599

Je ne sais pas si quelqu'un est toujours à la recherche d'une solution à ce problème, mais cela a été soulevé à plusieurs reprises pour moi, car j'écris un outil dans Unity pour le support de certains jeux et en raison de l'interopérabilité limitée de certains systèmes en mode mono. (comme PIA pour lire du texte à partir de Word, par exemple), je dois souvent écrire des exécutables spécifiques à un système d’exploitation (parfois Windows, parfois MacOS) et les lancer à partir de Process.Start ().

Le problème est que, lorsque vous lancez un exécutable comme celui-ci, il se lance dans un autre thread qui bloque votre application principale, ce qui provoque un blocage. Si vous souhaitez fournir à vos utilisateurs des commentaires utiles au cours de cette période, au-delà des icônes en rotation évoquées par votre système d'exploitation, alors vous êtes un peu foutu. L'utilisation d'un flux ne fonctionnera pas car le thread est toujours bloqué jusqu'à la fin de l'exécution.

La solution que j’ai choisie, qui peut paraître extrême pour certaines personnes, mais qui me convient parfaitement, consiste à utiliser des sockets et du multithreading pour configurer des communications synchrones fiables entre les deux applications. Bien sûr, cela ne fonctionne que si vous créez les deux applications. Sinon, je pense que vous n'avez pas de chance. ... J'aimerais voir si cela fonctionne uniquement en multithreading en utilisant une approche traditionnelle, donc si quelqu'un souhaite l'essayer et publier les résultats ici, ce serait formidable.

Quoi qu'il en soit, voici la solution qui fonctionne actuellement pour moi:

Dans l'application principale ou en appelant, je fais quelque chose comme ceci:

/// <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();

Voici où j'établis le serveur 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;
}

Voici mon gestionnaire de socket pour le thread ... Notez que vous devrez créer plusieurs threads dans certains cas; C'est pourquoi j'ai la liste _builderCommThreads dans cette liste (je l'ai transférée depuis du code ailleurs où je faisais quelque chose de similaire, mais en appelant plusieurs instances à la suite):

/// <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;
}

Bien sûr, vous devrez déclarer certaines choses en haut:

private TcpListener _listener = null;
private IPAddress _ipAddress = null;
private List<Thread> _builderCommThreads = null;
private System.Object _threadLock = new System.Object();

... puis dans l'exécutable invoqué, configurez l'autre extrémité (j'ai utilisé la statique dans ce cas, vous pouvez utiliser ce que vous voulez):

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);
    }
}

... J'utilise ceci pour lancer un outil de ligne de commande sous Windows qui utilise les éléments PIA pour extraire du texte d'un document Word. J'ai essayé PIA les .dlls dans Unity, mais j'ai rencontré des problèmes d'interopérabilité avec mono. Je l'utilise également sous MacOS pour appeler des scripts Shell qui lancent des instances Unity supplémentaires en mode batch et exécuter des scripts d'éditeur dans les instances qui communiquent avec l'outil via cette connexion de socket. C'est formidable, car je peux maintenant envoyer des commentaires à l'utilisateur, déboguer, surveiller et répondre à des étapes spécifiques du processus, et cetera, et cetera.

HTH

5
user2848240

J'ai utilisé la classe CmdProcessor décrite ici sur plusieurs projets avec beaucoup de succès. Cela semble un peu intimidant au début, mais il est très facile à utiliser.

1
joebalt

Essaye ça.

        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();

Laissez ce qui précède être dans une thread.

Maintenant, pour mettre à jour la sortie vers l'interface utilisateur, vous pouvez utiliser une timer avec deux lignes

  Console.Clear();
  Console.WriteLine(output);

Cela peut vous aider

0
sonu thomas