7
votes

Pourquoi le même code est-il tellement plus lent dans le fil du travail d'arrière-plan que dans mon fil d'interface graphique?

J'essaie de créer une application C # Winforms qui recherche et met en évidence le texte dans une richtextbox. J'ai créé deux méthodes de recherche: une qui fonctionne dans le fil de l'interface graphique et une qui fonctionne dans un travailleur d'arrière-plan. La logique dans les deux méthodes est essentiellement identique. Cependant, le code dans le BGW fonctionne considérablement plus lentement.

Veuillez voir les résultats ci-dessous: p>

0.25MB fichier texte recherchant un mot clé commun: GUI: 2.9S - BGW: 7.0
Fichier texte de 1 Mo Recherche sur un mot-clé commun: GUI: 14.1S - BGW: 71.4S
Fichier texte de 5 Mo Recherche sur un mot clé commun: GUI: 172s - BGW: 1545S P>

Il me semble étrange que la relation entre le temps pris pour les deux méthodes n'est pas une doublure en ce qui concerne la taille de la recherche. P >

L'application sera utilisée pour rechercher des fichiers jusqu'à 10 Mo de taille de 10 Mo afin qu'il soit important que cela soit rapide. Je voulais utiliser un ouvrier d'arrière-plan afin que l'utilisateur puisse voir les progrès et continuer à lire le fichier pendant la recherche de la recherche. P>

Veuillez consulter le code des deux méthodes ci-dessous: P>

    // 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);
        }
    }
  • Je sais que la classe RTB a sa propre méthode de recherche, mais la présente considérablement plus lente que ma propre méthode. li>
  • J'ai lu un certain nombre de threads concernant la performance BGW et le plus semblent utiliser l'utilisation des méthodes d'appel comme la cause, mais j'utilise aucun. li>
  • Je comprends que l'utilisation de threads multiples le rendra plus lente mais ne s'attendait pas à cette différence. li>
  • Le problème n'est pas avec ReportProgress Code> J'ai commenté cette ligne de sortie. La raison pour laquelle je le fais de cette façon plutôt que comme un pourcentage est le calcul pour déterminer le pourcentage de la grande différence. Il est en fait plus rapide de cette façon li>
  • this lien fourni par un autre utilisateur décrit comment J'utilise mon RTB dans un fil non influencé. Il semble suggérer que cela ne devrait pas être un problème, mais incitera plus de frais généraux car cela provoquera la création d'une file d'attente de message. Je ne sais pas si cela affectera la performance du code dans ma boucle foreach. Toute commentaires sur la question serait très appréciée. Li> ul> p>


4 commentaires

Peut-être que la priorité du fil d'antécédents est-elle aussi faible? Aussi "Code essentiellement identique" n'est pas un code identique.


@Gcaiazzo merci pour le commentaire. J'ai essayé de définir la priorité comme ceci: system.diagnostiques.process.getcurrentProcess (). Priorityclass = système.diagnostics.processpriorityclass.high; mais il ne semblait pas faire une différence. (Je comprends que c'est une mauvaise idée, car le fil est mis en commun. Je viens de le faire comme test). Quand j'ai dit essentiellement identique, je faisais référence à la logique dans la boucle de Foreach. Qui est la même chose. Je pense ^^


Le code que je regarde est en fait une mauvaise chose (TM). Le premier problème est que vous créez un contrôle ( richtextbox ) sur un fil d'arrière-plan. En règle générale, créez une commande uniquement sur le fil d'interface utilisateur principal. Lorsque vous créez un contrôle sur un fil d'arrière-plan, vous faites une tonne de merde dans l'arrière-plan qui devrait pas être fait sur un fond fil. Au lieu de cela, passez une chaîne sur votre fil d'arrière-plan et posez vos indices de surbrillance de votre retour de filetage afin que votre filetage de premier plan puisse mettre en évidence les blocs de texte le fil d'arrière-plan trouvé.


Gardez à l'esprit que l'interface utilisateur ne va pas mettre à jour beaucoup plus rapidement que chaque 20ms (environ 60 fois par seconde) - même si c'est le cas, pourquoi voudriez-vous? Donc, si vous êtes dans un ouvrier d'arrière-plan, effectuez des progrès accomplis tous les deux, vous passez beaucoup de temps à remercier des données sur le fil de l'interface utilisateur - ce qui n'est pas gratuit. Vous pouvez essayer de mettre à jour les données aussi rapidement que possible dans le travailleur d'arrière-plan, puis un fil de minuterie d'UI obtient les données quelques fois une seconde - ce qui pourrait réduire la quantité de maréchalage.


3 Réponses :


0
votes

Une chose qui ralentit généralement Winforms Down est la synchronisation avec le fil de l'interface utilisateur. Si le rapport de reportage fait cela (je ne sais pas mais je suppose que cela doit) et vous l'appelez trop souvent (disons 100-1000 fois une seconde ou plus), il ralentira tout à une halte de meulage en raison des divers problèmes de blocage cela se produira.

Essayez d'éliminer l'interaction entre l'interface utilisateur et le fil d'arrière-plan que vous avez et si cela aide, rétablir l'interaction mais que cela se produise beaucoup moins souvent, comme 1-100 fois par seconde.

En outre, je ne suis pas sûr, mais si vous passez une référence à un objet de contrôle, il pourrait toujours appartenir au fil de l'interface utilisateur et chaque interaction avec elle d'un autre thread pourrait également causer des problèmes de synchronisation (et d'interaction avec un Le contrôle des formulaires réels lancerait une exception).


1 commentaires

Merci pour votre réponse. J'ai essayé ce que vous avez suggéré, mais malheureusement, la performance n'est pas vraiment améliorée. J'ai supprimé toutes les références aux objets de thread de GUI dans la boucle ( travailleur.reportProgress () et searchRes.add () ). C'est ce que je passe au fil string parsedtext = richtextbox1.rtf; backworker1.runworkerasync (parsedtext); parsedtext est une variable globale de mon Form1 objet.



0
votes

Pas sûr ..., mais chaque fois que vous appelez le pointet sur la sélection de la SELECTRTF, un grand nombre de choses se produit, y compris d'obtenir un codage unicode de la chaîne, l'écrivant à un tampon, puis un grand nombre de messages Windows sont envoyés. .

Donc, tout d'abord, si vous pouvez redéfinir l'algorithme pour faire autant que possible sans accéder à la zone de recherche RTF, puis lotez la surbrillance, vous améliorerez probablement les performances. P>

sur le pourquoi Il est plus lent ... Les cases RTF sont créées sur un fil d'arrière-plan. Cela peut être quand ils envoient des messages et il n'y a pas de boucle de message pour les traiter, il y a un retard. Ou peut-être qu'il y a un peu de repasse à la bonne synchronisationContextContext qui prend le temps. Pas sûr. P>

Un profileur qui profite de votre propre code et un code-cadre .NET doit vous dire. P>

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 commentaires

0
votes

ne conviendra pas dans un commentaire, je vais donc poster une réponse.

Je n'ai pas utilisé Winforms depuis des siècles, mais il ne faut pas Winforms lancer une erreur pour accéder à un élément UI du code non-UI? Je me souviens de devoir faire un tas de cela.invoke , mais peut-être que le travailleur d'arrière-plan gère les choses différemment.

Quoi qu'il en soit, je suppose que la partie principale du temps supplémentaire consiste à synchroniser avec le fil d'interface utilisateur dans l'utilisateur pour accéder au RichTextBox. Faites ressortir le bon vieux chronomètre et mesurez votre code pour voir où se trouve le Botleneck.

Je me demande s'il serait plus rapide de séparer du texte dans des morceaux et d'utiliser plusieurs threads - Travail de noyau quadrières;) - Pour trouver tous les matchs, puis à la fin, passez au fil de l'interface utilisateur, itérer à travers toutes les correspondances et mettez en surbrillance le Texte.

Il devrait également être possible de mettre en surbrillance uniquement du texte sur la zone visible de l'écran et lorsque l'utilisateur fait défiler sur Higlight Downt texte ...


0 commentaires