2011-11-25 16 views
5

Il ragazzo di Business Intelligence qui con abbastanza C# sotto la cintura è pericoloso.Aggiornamento del thread UI (casella di testo) tramite C#

Ho creato un'applicazione di winform homebrew che essenzialmente esegue uno strumento da riga di comando in un ciclo per "fare cose". Detto materiale potrebbe essere completato in secondi o minuti. Normalmente avrò bisogno di eseguire lo strumento una volta per ogni riga che si trova in un DataTable.

Devo reindirizzare l'output dello strumento da riga di comando e visualizzarlo nella "mia" app. Sto tentando di farlo tramite una casella di testo. Sto incontrando problemi relativi all'aggiornamento del thread dell'interfaccia utente che non riesco a chiarire da solo.

Per eseguire il mio strumento da riga di comando, ho preso in prestito il codice da qui: How to parse command line output from c#?

Qui è il mio equivalente:

 private void btnImport_Click(object sender, EventArgs e) 
     { 
      txtOutput.Clear(); 
      ImportWorkbooks(dtable); 

     } 


     public void ImportWorkbooks(DataTable dt) 
     { 

      ProcessStartInfo cmdStartInfo = new ProcessStartInfo(); 
      cmdStartInfo.FileName = @"C:\Windows\System32\cmd.exe"; 
      cmdStartInfo.RedirectStandardOutput = true; 
      cmdStartInfo.RedirectStandardError = true; 
      cmdStartInfo.RedirectStandardInput = true; 
      cmdStartInfo.UseShellExecute = false; 
      cmdStartInfo.CreateNoWindow = false; 

      Process cmdProcess = new Process(); 
      cmdProcess.StartInfo = cmdStartInfo; 
      cmdProcess.ErrorDataReceived += cmd_Error; 
      cmdProcess.OutputDataReceived += cmd_DataReceived; 
      cmdProcess.EnableRaisingEvents = true; 
      cmdProcess.Start(); 
      cmdProcess.BeginOutputReadLine(); 
      cmdProcess.BeginErrorReadLine(); 

      //Login 
      cmdProcess.StandardInput.WriteLine(BuildLoginString(txtTabCmd.Text, txtImportUserName.Text, txtImportPassword.Text, txtImportToServer.Text)); 


      foreach (DataRow dr in dt.Rows) 
      { 
        cmdProcess.StandardInput.WriteLine(CreateServerProjectsString(dr["Project"].ToString(), txtTabCmd.Text)); 

       //Import Workbook 

       cmdProcess.StandardInput.WriteLine(BuildPublishString(txtTabCmd.Text, dr["Name"].ToString(), dr["UID"].ToString(),dr["Password"].ToString(), dr["Project"].ToString())); 
      } 
      cmdProcess.StandardInput.WriteLine("exit"); //Execute exit. 
      cmdProcess.EnableRaisingEvents = false; 
      cmdProcess.WaitForExit(); 
     } 


private void cmd_DataReceived(object sender, DataReceivedEventArgs e) 
     { 
      //MessageBox.Show("Output from other process"); 
      try 
      { 
     // I want to update my textbox here, and then position the cursor 
     // at the bottom ala: 

       StringBuilder sb = new StringBuilder(txtOutput.Text); 
       sb.AppendLine(e.Data.ToString()); 
       txtOutput.Text = sb.ToString(); 
       this.txtOutput.SelectionStart = txtOutput.Text.Length; 
       this.txtOutput.ScrollToCaret(); 


      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("{0} Exception caught.", ex); 

      } 

     } 

Riferimento txtOuput.text quando un'istanza di mia StringBuilder in cmd_DataReceived() fa sì che l'app si blocchi appieno: sto indovinando una sorta di problema cross-thread.

Se rimuovo il riferimento alla txtOuput.text in StringBuilder e continuare il debug, ottengo una violazione cross-thread qui:

txtOutput.Text = sb.ToString(); 

Cross-thread operation not valid: Control 'txtOutput' accessed from a thread other than the thread it was created on. 

OK, non sorpreso. Ho ipotizzato che cmd_DataReceived sia in esecuzione su un altro thread poiché lo sto colpendo come risultato di fare cose dopo un Process.Start() ... e se rimuovo TUTTI i riferimenti a txtOuput.Text in cmd_DataReceived() e semplicemente scarico il testo della riga di comando output alla console tramite Console.Write(), tutto funziona correttamente.

Così, la prossima ho intenzione di provare le tecniche standard per aggiornare il mio TextBox sul thread dell'interfaccia utente utilizzando le informazioni in http://msdn.microsoft.com/en-us/library/ms171728.aspx

aggiungo un delegato e filo per la mia classe:

delegate void SetTextCallback(string text); 
// This thread is used to demonstrate both thread-safe and 
// unsafe ways to call a Windows Forms control. 
private Thread demoThread = null; 

I aggiungere una procedura per aggiornare la casella di testo:

private void SetText(string text) 
    { 
     // InvokeRequired required compares the thread ID of the 
     // calling thread to the thread ID of the creating thread. 
     // If these threads are different, it returns true. 
     if (this.txtOutput.InvokeRequired) 
     { 
      SetTextCallback d = new SetTextCallback(SetText); 
      this.Invoke(d, new object[] { text }); 
     } 
     else 
     { 
      this.txtOutput.Text = text; 
     } 
    } 

aggiunge un'altra proc che richiama il filo-safe uno:

private void ThreadProcSafe() 
    { 
     // this.SetText(sb3.ToString()); 
     this.SetText("foo"); 

    } 

... e alla fine io chiamo questo pasticcio all'interno cmd_DataReceived come questo:

private void cmd_DataReceived(object sender, DataReceivedEventArgs e) 
{ 
    //MessageBox.Show("Output from other process"); 
    try 
    { 

     sb3.AppendLine(e.Data.ToString()); 

     //this.backgroundWorker2.RunWorkerAsync(); 
     this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafe)); 
     this.demoThread.Start(); 
     Console.WriteLine(e.Data.ToString()); 

    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("{0} Exception caught.", ex); 


    } 

} 

... Quando ho eseguito questo testo, la casella di testo sta lì morto stecchito, non sempre aggiornato. La mia finestra della console continua ad aggiornarsi. Come potete vedere, ho provato a semplificare un po 'le cose semplicemente facendo in modo che la casella di testo visualizzasse "foo" rispetto all'output reale dello strumento, ma non alla gioia. La mia interfaccia utente è morta.

Quindi cosa dà? Non riesco a capire cosa sto facendo male. Non sono affatto sposato a mostrare risultati in una casella di testo, btw - Devo solo essere in grado di vedere cosa succede dentro l'applicazione e preferirei non aprire un'altra finestra per farlo.

Molte grazie.

risposta

0

uno dei motivi per cui la casella di testo non viene aggiornata è perché non si passa la stringa al metodo SetText.

Non è necessario creare una discussione. L'implementazione di SetText gestirà il passaggio della chiamata dal thread di lavoro (dove viene chiamato cmd_DataReceived) al thread dell'interfaccia utente.

Ecco cosa ti suggerisco di fare:

private void cmd_DataReceived(object sender, DataReceivedEventArgs e) 
{ 
    //MessageBox.Show("Output from other process"); 
    try 
    { 


     string str = e.Data.ToString(); 
     sb3.AppendLine(str); 
     SetText(str); //or use sb3.ToString if you need the entire thing 

     Console.WriteLine(str); 

    } 
    catch (Exception ex) 
    { 
     Console.WriteLine("{0} Exception caught.", ex); 


    } 

} 

Inoltre, si stanno bloccando il thread UI come @Fischermaen menzionato quando si chiama WaitForExit, non hai bisogno di esso.

Vorrei anche suggerire che si esegue ImportWorkbooks su un thread di lavoro in questo modo: (se si esegue questa operazione è possibile lasciare la chiamata a WaitForExit)

private void btnImport_Click(object sender, EventArgs e) 
{ 
    txtOutput.Clear(); 
    ThreadPool.QueueUserWorkItem(ImportBooksHelper, dtTable); 
} 

private ImportBooksHelper(object obj) 
{ 
    DataTable dt = (DataTable)obj; 
    ImportWorkbooks(dtable); 
} 
+0

Ma la casella di testo non verrà ancora aggiornata, perché il thread dell'interfaccia utente è bloccato dalla riga 'cmdProcess.WaitForExit();'. – Fischermaen

+0

Grazie, proverò a combinare il tuo suggerimento con quello di Fisherman e KooKiz. –

3

Stai chiamando ImportWorkbooks dal thread dell'interfaccia utente. Quindi, con questo metodo, stai chiamando "cmdProcess.WaitForExit()". Quindi in pratica stai bloccando il thread dell'interfaccia utente fino al completamento dell'esecuzione del processo.Esegui ImportWorkbooks da un thread e dovrebbe funzionare, oppure rimuovere WaitForExit e utilizzare invece l'evento "Exited" del processo.

+0

Grazie, KooKiz! - Posso avviare ImportWorkbooks() su un thread diverso usando la stessa tecnica di base di cmd_DataReceived()? Thread.Start(), in sostanza? –

+0

@RussellChristopher Sì, poiché ImportWorkbooks non interagisce con l'interfaccia utente, è possibile eseguirlo in un thread "semplice", utilizzando Thread.Start. –

4

Credo che il problema è in questa linea:

cmdProcess.WaitForExit(); 

E 'nel metodo ImportWorkbooks che viene chiamato dal metodo Click di btnImport. Quindi il tuo thread UI è bloccato fino al completamento del processo in background.

Problemi correlati