2011-12-10 15 views
6

Sto provando a creare un'applicazione C# WinForms che cerca ed evidenzia il testo in un RichTextBox. Ho creato due metodi di ricerca: uno che viene eseguito nel thread GUI e uno che viene eseguito in un BackGroundWorker. La logica in entrambi i metodi è essenzialmente identica. Tuttavia, il codice nel BGW funziona molto più lentamente.Perché lo stesso codice è molto più lento nel mio thread BackGroundWorker che nel mio thread GUI?

Si prega di consultare i risultati qui sotto:

File di testo 0.25MB ricerca di una parola chiave comune: GUI: 2.9s - BGW: 7.0s
File di testo 1MB ricerca di una parola chiave comune: GUI: 14.1s - BGW: 71,4 s
5 MB di file di testo alla ricerca di una parola chiave comune: GUI: 172S - BGW: 1545s

sembra strano a me che il rapporto tra il tempo impiegato per i due metodi non tratta di rivestimento in materia di perquisizione dimensioni.

L'applicazione verrà utilizzata per la ricerca di file fino a 10 MB di dimensioni, quindi è importante che sia veloce. Volevo utilizzare un lavoratore in background in modo che l'utente potesse vedere l'avanzamento e continuare a leggere il file mentre viene eseguita la ricerca.

Si prega di vedere il codice per i due metodi indicati

// background search thread 
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
    { 
     // Get the BackgroundWorker that raised this event. 
     BackgroundWorker worker = sender as BackgroundWorker; 

     RichTextBox rtb = new RichTextBox(); 
     RichTextBox results = new RichTextBox(); 
     rtb.Rtf = e.Argument as string; //recive text to be searched 

     int hits = 0; // track number of hits 
     int pos = 0; // track position in rtb 
     int i = 0; // trach current line number for progress report 

     string lowerT = searchTerm.ToLowerInvariant(); 
     string lowerl = ""; 
     int n = 0; 
     int len = searchTerm.Length; 

     foreach (string l in rtb.Lines) 
     { 
      lowerl = l.ToLowerInvariant(); 
      n = lowerl.IndexOf(lowerT); 
      if (n > -1) 
      { 
       while (n > -1) //if found sterm highlight instances 
       { 
        hits++;  //incriment hits 

        //hilight term 
        rtb.SelectionStart = pos + n; 
        rtb.SelectionLength = len; 
        rtb.SelectionBackColor = Color.Yellow; 
        rtb.SelectionColor = Color.Black; 

        //find next 
        n = lowerl.IndexOf(lowerT, n + len); 
       } 
       searchRes.Add(pos); // add positon of hit to results list 

       //add rtb formatted text to results rtb 
       rtb.SelectionStart = pos; 
       rtb.SelectionLength = l.Length; 
       results.SelectedRtf = rtb.SelectedRtf; 
       results.AppendText(Environment.NewLine); 

      } 
      pos += l.Length + 1; //incriment position 

      //worker.ReportProgress(++i); 
     } 
     string[] res = {rtb.Rtf,results.Rtf,hits.ToString()}; 
     e.Result = res; 
    } 

    // old non threaded search method 
    public void OldSearch(string sTerm) 
    { 
     int hits = 0; // track number of hits 
     int pos = 0; // track position in rtb 
     int oldPos = richTextBox1.SelectionStart; //save current positin in rtb 
     int oldLen = richTextBox1.SelectionLength; 

     string lowerT = sTerm.ToLowerInvariant(); 

     sTime = 0; 
     System.Threading.Timer tmr = new System.Threading.Timer(new TimerCallback(TimerTask), null, 0, 100); 

     if (sTerm.Length > 0) 
     { 
      //clear old search 
      ReloadFile(); 
      richTextBox4.Clear(); 
      searchRes = new List<int>(); 

      //open results pane 
      label1.Text = "Searching for \"" + sTerm + "\"..."; 
      splitContainer1.Panel2Collapsed = false; 

      frmFind.Focus(); 
      frmFind.ShowProgress(true); 

      foreach (string l in richTextBox1.Lines) 
      { 
       string lowerl = l.ToLowerInvariant(); 
       int n = lowerl.IndexOf(lowerT); 
       if (n > -1) 
       { 
        while (n > -1) //if found sterm highlight instances 
        { 
         hits++;  //incriment hits 
         //hilight term 
         richTextBox1.SelectionStart = pos + n; 
         richTextBox1.SelectionLength = sTerm.Length; 
         richTextBox1.SelectionBackColor = Color.Yellow; 
         richTextBox1.SelectionColor = Color.Black; 
         //find next 
         n = lowerl.IndexOf(lowerT, n + sTerm.Length); 
        } 
        searchRes.Add(pos); 
        richTextBox1.SelectionStart = pos; 
        richTextBox1.SelectionLength = l.Length; 
        richTextBox4.SelectedRtf = richTextBox1.SelectedRtf; 
        richTextBox4.AppendText(Environment.NewLine); 
       } 
       pos += l.Length + 1; //incriment position 
      } 

      tmr.Dispose(); 

      float time = (float)sTime/10; 

      label1.Text = "Search for \"" + sTerm + "\": Found " + hits + " instances in " + time + " seconds."; 
      richTextBox4.SelectionStart = 0; 
      richTextBox1.SelectionStart = oldPos; 
      richTextBox1.SelectionLength = oldLen; 
      richTextBox1.Focus(); 
      frmFind.ShowProgress(false); 
     } 
    } 

NOTE:

  • So che la classe RTB ha il proprio metodo find, ma trovato questo per essere notevolmente più lento della mia metodo.
  • Ho letto un numero di thread relativi alle prestazioni BGW e la maggior parte sembra utilizzare come metodo i metodi Invoke ma non ne utilizzo nessuno.
  • Capisco che l'uso di più thread lo farà rallentare ma non si aspettava questa differenza.
  • Il problema non è con ReportProgress ho commentato questa linea. Il motivo per cui lo sto facendo in questo modo piuttosto che in percentuale è il calcolo per calcolare la percentuale ha fatto una grande differenza. In questo modo è più veloce in questo modo
  • Questo link fornito da un altro utente descrive come sto usando il mio RTB in un thread non GUI. Sembra suggerire che non dovrebbe essere un problema, ma si incorre in ulteriori spese generali poiché causerebbe la creazione di una coda di messaggi. Non sono sicuro che ciò influenzerà le prestazioni del codice all'interno del mio ciclo foreach. Qualsiasi commento sull'argomento sarebbe molto apprezzato.
+0

Forse la priorità del thread in background è troppo basso? Anche "codice sostanzialmente identico" non è un codice identico. – GCaiazzo

+0

@GCaiazzo Grazie per il commento. Ho provato a impostare la priorità in questo modo: 'System.Diagnostics.Process.GetCurrentProcess(). PriorityClass = System.Diagnostics.ProcessPriorityClass.High;' ma non sembrava fare la differenza. (Capisco che questa sia una cattiva idea, dato che il thread è in pool. L'ho fatto solo come test). Quando ho detto essenzialmente identico mi riferivo alla logica del ciclo foreach. Che è lo stesso Penso ^^ – mfa

+0

Il codice che sto guardando è in realtà una cosa cattiva (tm). Il primo problema è che stai creando un 'Control' (' RichTextBox') su un thread in background. Come regola generale, creare SOLO un controllo sul thread principale dell'interfaccia utente. Quando crei un 'Control' su un thread in background, stai facendo un _ton_ di crap sullo sfondo che non dovrebbe fare __not_ su un thread in background. Invece, passa una stringa al thread in background e il tuo thread in background restituisce gli indici di evidenziazione in modo che il thread in primo piano possa evidenziare i blocchi di testo trovati nel thread in background. –

risposta

0

Una cosa che generalmente rallenta le Winforms è la sincronizzazione con il thread dell'interfaccia utente. Se ReportProgress lo fa (non lo so, ma credo che debba farlo) e lo chiami troppo spesso (diciamo 100-1000 volte al secondo o più) rallenterà tutto fino a fermarsi a causa dei vari problemi di blocco ciò accadrà.

Prova a rimuovere qualsiasi interazione tra l'interfaccia utente e il thread in background che hai e se ciò aiuta, ripristina l'interazione ma lascia che accada molto meno spesso, come 1-100 volte al secondo.

Inoltre, non sono sicuro, ma se stai passando un riferimento a un oggetto di controllo, potrebbe ancora essere di proprietà del thread dell'interfaccia utente e ogni interazione con esso da un altro thread potrebbe anche causare problemi di sincronizzazione (e interazione con un controllo effettivo delle forme genererebbe un'eccezione).

+0

Grazie per la risposta. Ho provato quello che hai suggerito ma sfortunatamente le prestazioni non sono migliorate. Ho rimosso tutti i riferimenti agli oggetti thread della GUI all'interno del ciclo ('worker.ReportProgress()' e 'searchRes.Add()'). Questo è ciò che sto passando al thread 'string parsedText = richTextBox1.Rtf; backgroundWorker1.RunWorkerAsync (parsedText); '' parsedText' è una variabile globale del mio oggetto 'Form1'. – mfa

0

Non sicuro ..., ma ogni volta che chiami il setter su SelectedRtf, succede un sacco di cose, incluso ottenere una codifica unicode della stringa, scriverlo su un buffer e poi inviare un sacco di messaggi di Windows.

Quindi, in primo luogo, se è possibile ridisegnare l'algoritmo per fare quanto possibile senza accedere alla casella di ricerca RTF e quindi aumentare l'evidenziazione, probabilmente si miglioreranno le prestazioni.

Per quanto riguarda il motivo per cui è più lento ... le caselle RTF vengono create su un thread in background. Potrebbe essere quando inviano messsages e non c'è un loop di messaggi per elaborarli, c'è un ritardo. O forse c'è un po 'di marshalling sul giusto SynchronizationContext che prende il tempo. Non sono sicuro.

Un profiler che profili il proprio codice e il codice .NET Framework dovrebbe dirvi comunque.

public string SelectedRtf 
    { 
     get 
     { 
     this.ForceHandleCreate(); 
     return this.StreamOut(32770); 
     } 
     set 
     { 
     this.ForceHandleCreate(); 
     if (value == null) 
      value = ""; 
     this.StreamIn(value, 32770); 
     } 
    } 

private void StreamIn(string str, int flags) 
{ 
    if (str.Length == 0) 
    { 
    if ((32768 & flags) != 0) 
    { 
     this.SendMessage(771, 0, 0); 
     this.ProtectedError = false; 
    } 
    else 
     this.SendMessage(12, 0, ""); 
    } 
    else 
    { 
    int length = str.IndexOf(char.MinValue); 
    if (length != -1) 
     str = str.Substring(0, length); 
    byte[] buffer = (flags & 16) == 0 ? Encoding.Default.GetBytes(str) : Encoding.Unicode.GetBytes(str); 
    this.editStream = (Stream) new MemoryStream(buffer.Length); 
    this.editStream.Write(buffer, 0, buffer.Length); 
    this.editStream.Position = 0L; 
    this.StreamIn(this.editStream, flags); 
    } 
} 

private void StreamIn(Stream data, int flags) 
{ 
    if ((flags & 32768) == 0) 
    System.Windows.Forms.UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), 1079, 0, new System.Windows.Forms.NativeMethods.CHARRANGE()); 
    try 
    { 
    this.editStream = data; 
    if ((flags & 2) != 0) 
    { 
     long position = this.editStream.Position; 
     byte[] numArray = new byte[RichTextBox.SZ_RTF_TAG.Length]; 
     this.editStream.Read(numArray, (int) position, RichTextBox.SZ_RTF_TAG.Length); 
     string @string = Encoding.Default.GetString(numArray); 
     if (!RichTextBox.SZ_RTF_TAG.Equals(@string)) 
     throw new ArgumentException(System.Windows.Forms.SR.GetString("InvalidFileFormat")); 
     this.editStream.Position = position; 
    } 
    System.Windows.Forms.NativeMethods.EDITSTREAM editstream = new System.Windows.Forms.NativeMethods.EDITSTREAM(); 
    int num1 = (flags & 16) == 0 ? 5 : 9; 
    int num2 = (flags & 2) == 0 ? num1 | 16 : num1 | 64; 
    editstream.dwCookie = (IntPtr) num2; 
    editstream.pfnCallback = new System.Windows.Forms.NativeMethods.EditStreamCallback(this.EditStreamProc); 
    this.SendMessage(1077, 0, int.MaxValue); 
    if (IntPtr.Size == 8) 
    { 
     System.Windows.Forms.NativeMethods.EDITSTREAM64 editstreaM64 = this.ConvertToEDITSTREAM64(editstream); 
     System.Windows.Forms.UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), 1097, flags, editstreaM64); 
     editstream.dwError = this.GetErrorValue64(editstreaM64); 
    } 
    else 
     System.Windows.Forms.UnsafeNativeMethods.SendMessage(new HandleRef((object) this, this.Handle), 1097, flags, editstream); 
    this.UpdateMaxLength(); 
    if (this.GetProtectedError()) 
     return; 
    if (editstream.dwError != 0) 
     throw new InvalidOperationException(System.Windows.Forms.SR.GetString("LoadTextError")); 
    this.SendMessage(185, -1, 0); 
    this.SendMessage(186, 0, 0); 
    } 
    finally 
    { 
    this.editStream = (Stream) null; 
    } 
} 
0

Non rientrano in un commento, quindi posterò una risposta.

Non utilizzo WinForms da anni, ma WinForm non dovrebbe generare un errore per l'accesso a un elemento dell'interfaccia utente dal codice non UI? Ricordo di dover fare un po 'di roba this.Invoke, ma forse il backgroundworker gestisce le cose in modo diverso.

In ogni caso, la mia ipotesi è che la parte principale del tempo supplementare si sincronizzi con il thread dell'interfaccia utente per accedere a RichTextBox. Tira fuori il buon vecchio cronometro e misura il tuo codice per vedere dove si trova il collo di bottiglia.

Mi chiedo se sarebbe più veloce separare il testo in blocchi e utilizzare più thread - lavoro quad-core;) - per trovare tutte le corrispondenze e quindi alla fine passare al thread dell'interfaccia utente, scorrere tutte le corrispondenze e evidenziare il testo.

Dovrebbe anche essere possibile solo evidenziare il testo sulla zona visibile dello schermo e quando l'utente scorre per higlight ulteriormente il testo ...

Problemi correlati