RichTexBox - szybkie kolorowanie lini

0

Dzien dobry,

Mam w aplikacji RichTexBox ktory zaiwera okolo 10tys. lini, potrzebuje w liniach ktore zawieraja wyszukiwany text zmienic kolor. Niestety moja procedura dziala bardzo wolno ;(.
Czy jest jakas metoda szybszego wyszukiwania tekstu w Richtextboxie? Wyszukiwanie I kolorowanie wykonywane jest w osobnym watku w Thread pool co tym bardziej nie pomaga, a I tak spowalnia dzialanie na GUI. Przetwarza okolo 1000lini na minute.
rtbProtocol to Rich box w GUI

private void UpdateProtocolSetup(object sender, EventArgs e)
       {
          
              
          Task.Run(() => {
               int i = 0;
              int imax = (int)this.Invoke(new Func<int>(() => rtbProtocol.Lines.Length));



              for (i = 0; i < imax; i++)
               {
                  SetText(label15, i.ToString());
         bool hlExce = (bool)this.Invoke(new Func<bool>(() => (rtbProtocol.Lines[i].Contains("EXCE") & cbxExceProtocol.Checked)));
         bool hlSql = (bool)this.Invoke(new Func<bool>(() => (rtbProtocol.Lines[i].Contains("SQL") & cbxSqlProtocol.Checked)));
         bool hlOpc = (bool)this.Invoke(new Func<bool>(() => (rtbProtocol.Lines[i].Contains("OPC") & cbxOpcProtocol.Checked)));
         bool hlCfg = (bool)this.Invoke(new Func<bool>(() => (rtbProtocol.Lines[i].Contains("CFG") & cbxCfgProtocol.Checked)));
         bool hlPmon = (bool)this.Invoke(new Func<bool>(() => (rtbProtocol.Lines[i].Contains("PMON") & cbxPmonProtocol.Checked)));
         bool hlDem = (bool)this.Invoke(new Func<bool>(() => (rtbProtocol.Lines[i].Contains("DEM") & cbxDemProtocol.Checked)));
         bool hl = hlSql | hlOpc | hlCfg | hlPmon | hlDem;
                   if (hl | hlExce)
                   {
                      
                       int start = (int)this.Invoke(new Func<int>(() => rtbProtocol.GetFirstCharIndexFromLine(i)));
                       int end = (int)this.Invoke(new Func<int>(() => rtbProtocol.Lines[i].Length));
                       SelectRtb(rtbProtocol, start, end);
                       if (hlExce)
                           // rtbProtocol.SelectionColor = Color.Red;
                           SelectionColorRtb(rtbProtocol, Color.Red);
                       else
                           //rtbProtocol.SelectionColor = Color.BlueViolet;
                           SelectionColorRtb(rtbProtocol, Color.BlueViolet);
                       //rtbProtocol.SelectionLength = 0;
                       SelectionLengthRtb(rtbProtocol, 0);
                       
                  }
                   
                  //SetText(label15, i.ToString());
           }
           }); 
       }

       private void SetText(Control sender, string aText)
       {
           if (sender.InvokeRequired)
           {
               Invoke((Action<Control, string>) SetText, sender, aText);
               return;
           }
           sender.Text = aText;
       }

       private void SelectionColorRtb(RichTextBox aRtb, Color aColor)
       {
           if (aRtb.InvokeRequired)
           {
               Invoke((Action<RichTextBox, Color>)SelectionColorRtb, aRtb, aColor);
               return;
           }
           aRtb.SelectionColor = aColor;
       }

       private void SelectionLengthRtb(RichTextBox aRtb, int aSelectionLength)
       {
           if (aRtb.InvokeRequired)
           {
               Invoke((Action<RichTextBox, int>)SelectionLengthRtb, aRtb, aSelectionLength);
               return;
           }
           aRtb.SelectionLength = aSelectionLength;
       }

       private void SelectRtb(RichTextBox aRtb, int aStart, int aEnd)
       {
           if (aRtb.InvokeRequired)
           {
               Invoke((Action<RichTextBox, int, int>)SelectRtb, aRtb, aStart, aEnd);
               return;
           }
           aRtb.Select(aStart, aEnd);
       }

0

Ponizsza modificacja zredukowala czas 4 krotnie, jest lepiej ale ciagle dlugo

 string line = (string)this.Invoke(new Func<string>(() => (rtbProtocol.Lines[i])));
 bool hlExce = line.Contains("EXCE") & (bool)this.Invoke(new Func<bool>(() => cbxExceProtocol.Checked));
 bool hlSql = line.Contains("SQL") & (bool)this.Invoke(new Func<bool>(() => cbxSqlProtocol.Checked));
 bool hlOpc = line.Contains("OPC") & (bool)this.Invoke(new Func<bool>(() => cbxOpcProtocol.Checked));
 bool hlCfg = line.Contains("CFG") & (bool)this.Invoke(new Func<bool>(() => cbxCfgProtocol.Checked));
 bool hlPmon = line.Contains("PMON") & (bool)this.Invoke(new Func<bool>(() => cbxPmonProtocol.Checked));
 bool hlDem = line.Contains("DEM") & (bool)this.Invoke(new Func<bool>(() => cbxDemProtocol.Checked));```
0

Jest wiele rzeczy które można tutaj poprawić i to chociażby elementarnych jak po co odwoływać się w każdej iteracji pętli do stanu kontrolki (cbxExceProtocol.Checked) ?!
Robiąc coś w odzielnnym wątku chcemy być niezależni jak tyko się da, a te Twoje Invoki odowłujące się do UI zabijają kompletnie wydajność. Podjerzewam że napisanie tego calkowcie w watlku UI byłoby znacznie szybsze, od tego co wkleiłeś.

Więc jak zyć? Co zrobić?
Ograniczyć -> invoki do minimum -> podzielić tekst na partie powiedzmy po 100 linijek, wykryć wszystkie zmiany w danym fragmencie, a potem odpalić jednego asynchronicznego invoka który naniesie te zmiany na rtb, a nie odpalać kilkanascie invokow synchronicznych per linijke.

0
neves napisał(a):

Jest wiele rzeczy które można tutaj poprawić i to chociażby elementarnych jak po co odwoływać się w każdej iteracji pętli do stanu kontrolki (cbxExceProtocol.Checked) ?!

Przenislem przed for, jest lepiej ponad 4300lini/min. To bylo na poczatku w UI dlatego automatycznie dodalem invoke

neves napisał(a):

podzielić tekst na partie powiedzmy po 100 linijek, wykryć wszystkie zmiany w danym fragmencie, a potem odpalić jednego asynchronicznego invoka który naniesie te zmiany na rtb, a nie odpalać > kilkanascie invokow synchronicznych per linijke.

Jak dobrze zrozumielem:

  • zassac x lini z richboxa jednym invoke
  • wyszukac linie ze zmianami
  • pokolorowac w richbox nastepnym invoke ?

Dzieki jutro wyprobuje!!

0
neves napisał(a):

podzielić tekst na partie powiedzmy po 100 linijek,

Usjwo napisał(a):

Dzieki jutro wyprobuje!!

Zysk okazal sie pozorny ;(. Szybkosc nie wzrosla zauwazalnie, w zamian za to invoke zaiwsza praktycznie UI. Wieksza czesc czasu metoda "spedza" w UI thread kolorujac 100 lini. Mozliwe tez ze cos robie nie tak ;(.

 private void UpdateProtocolSetup(object sender, EventArgs e)
        {

            prbProtocolUpdate.Maximum = rtbProtocol.Lines.Length;
            prbProtocolUpdate.Value = 0;
            prbProtocolUpdate.Visible = true;
            Task.Run(() => {
                int i = 0;
                int imax = (int)this.Invoke(new Func<int>(() => rtbProtocol.Lines.Length));
                bool ExceChecked = (bool)this.Invoke(new Func<bool>(() => cbxExceProtocol.Checked));
                bool SqlChecked = (bool)this.Invoke(new Func<bool>(() => cbxSqlProtocol.Checked));
                bool OpcChecked = (bool)this.Invoke(new Func<bool>(() => cbxOpcProtocol.Checked));
                bool CfgChecked = (bool)this.Invoke(new Func<bool>(() => cbxCfgProtocol.Checked));
                bool PmonChecked = (bool)this.Invoke(new Func<bool>(() => cbxPmonProtocol.Checked));
                bool DemChecked = (bool)this.Invoke(new Func<bool>(() => cbxDemProtocol.Checked));
                string[] bufferRtb = new string[100];
                int[] lineColor = new int[100];
                int linePointer = 0;
                for (int j = 0; (j < imax / 100); j++)
                {
                    linePointer = j * 100;
                    GetDataFromRtb(rtbProtocol, linePointer, bufferRtb);
                    for (i = 0; i < 100; i++)
                    {
                        //string line = (string)this.Invoke(new Func<string>(() => (rtbProtocol.Lines[i])));
                        bool hlExce = bufferRtb[i].Contains("EXCE") & ExceChecked;
                        bool hlSql = bufferRtb[i].Contains("SQL") & SqlChecked;
                        bool hlOpc = bufferRtb[i].Contains("OPC") & OpcChecked;
                        bool hlCfg = bufferRtb[i].Contains("CFG") & CfgChecked;
                        bool hlPmon = bufferRtb[i].Contains("PMON") & PmonChecked;
                        bool hlDem = bufferRtb[i].Contains("DEM") & DemChecked;
                        bool hl = hlSql | hlOpc | hlCfg | hlPmon | hlDem;
                        if (hl | hlExce)
                        {
                            lineColor[i] = 0;
                            if (hlExce)
                                lineColor[i] = 1;
                            else
                                lineColor[i] = 2;
                        }
                    }
                    SetColorOnRtb(rtbProtocol, linePointer, lineColor);
                    ValuePrb(prbProtocolUpdate, j*100);
                }
                VisiblePrb(prbProtocolUpdate, false);
            });
        }

        private void SetColorOnRtb(RichTextBox aRtb, int aStartPointer, int[] aLineColor)
        {
            if (aRtb.InvokeRequired)
            {
                Invoke((Action<RichTextBox, int, int[]>)SetColorOnRtb, aRtb, aStartPointer, aLineColor);
                return;
            }
            for (int i = aStartPointer; (i < 100 + aStartPointer & i < aRtb.Lines.Length); i++)
            {
                if (aLineColor[i - aStartPointer] != 0)
                {
                    int start = aRtb.GetFirstCharIndexFromLine(i);
                    int end = aRtb.Lines[i].Length;
                    aRtb.Select(start, end);
                    if (aLineColor[i - aStartPointer] == 1)
                        aRtb.SelectionColor = Color.Red;
                    else
                        aRtb.SelectionColor = Color.BlueViolet;
                    aRtb.SelectionLength = 0;
                }
            }
               
        }

        private void GetDataFromRtb(RichTextBox aRtb, int aStartPointer, string[] aBuffer)
        {
            if (aRtb.InvokeRequired)
            {
                Invoke((Action<RichTextBox, int, string[]>)GetDataFromRtb, aRtb, aStartPointer, aBuffer);
                return;
            }
            for (int i = aStartPointer; (i < aStartPointer + 100 & i < aRtb.Lines.Length); i++)
            {
                aBuffer[i - aStartPointer] = aRtb.Lines[i];
            }
        }
0

Zszedłem do 72000 lini/min :) na moim przykładowym tekście, wąskim gardłem teraz je wyłącznie samo zaznaczanie i kolorowanie tekstu, dlatego samego wyszukiwania nie optymalizowałem, w ten spsób już raczej znacznie szybciej się nie da, alternatywnie spróbowałbym generować w tle dokument RTF i go wczytać gotowego do kontrolki.

Z ciekawostek które mi pokazał profiler, dostęp do rtbProtocol.Lines jest strasznie wolny!, dlatego w poniższym kodzie jest robiony tylko raz :)

const int BatchSize = 1000;
      

        private async void UpdateProtocolSetup(object sender, EventArgs e)
        {
            bool ExceChecked = cbxExceProtocol.Checked;
            bool SqlChecked = cbxSqlProtocol.Checked;
            bool OpcChecked = cbxOpcProtocol.Checked;
            bool CfgChecked = cbxCfgProtocol.Checked;
            bool PmonChecked = cbxPmonProtocol.Checked;
            bool DemChecked = cbxDemProtocol.Checked;
            string[] lines = rtbProtocol.Lines;             
          
            await Task.Run(() => {                

                int[] lineColor = null;

                for (int i = 0; i < lines.Length; ++i)
                {
                    int lineNumberInBatch = i % BatchSize;
                    if (lineNumberInBatch == 0)
                    {
                        if (lineColor != null)
                        {
                            SetColorOnRtb(rtbProtocol, i - BatchSize, i, lineColor, lines);
                        }
                        lineColor = new int[BatchSize];
                    }

                    string line = lines[i];

                    bool hlExce = line.Contains("EXCE") & ExceChecked;
                    bool hlSql = line.Contains("SQL") & SqlChecked;
                    bool hlOpc = line.Contains("OPC") & OpcChecked;
                    bool hlCfg = line.Contains("CFG") & CfgChecked;
                    bool hlPmon = line.Contains("PMON") & PmonChecked;
                    bool hlDem = line.Contains("DEM") & DemChecked;
                    bool hl = hlSql | hlOpc | hlCfg | hlPmon | hlDem;
                    if (hl | hlExce)
                    {
                        if (hlExce)
                            lineColor[lineNumberInBatch] = 1;
                        else
                            lineColor[lineNumberInBatch] = 2;
                    }
                }

                if (lineColor != null)
                {
                    SetColorOnRtb(rtbProtocol, ((lines.Length - 1) / BatchSize) * BatchSize, lines.Length, lineColor, lines);
                }
            });
        }


        private void SetColorOnRtb(RichTextBox aRtb, int aStartPointer, int aStopPointer, int[] aLineColor, string[] lines)
        {
            if (aRtb.InvokeRequired)
            {
                BeginInvoke((Action<RichTextBox, int, int, int[], string[]>)SetColorOnRtb, aRtb, aStartPointer, aStopPointer, aLineColor, lines);
                return;
            }
        
            for (int i = aStartPointer; i < aStopPointer; ++i)
            {
                if (aLineColor[i - aStartPointer] != 0)
                {  
                    int start = aRtb.GetFirstCharIndexFromLine(i);
                    int end = lines[i].Length;
                    aRtb.Select(start, end);
                    if (aLineColor[i - aStartPointer] == 1)
                        aRtb.SelectionColor = Color.Red;
                    else
                        aRtb.SelectionColor = Color.BlueViolet;
                    aRtb.SelectionLength = 0;
                }
            }
        }
1

                bool ExceChecked = (bool)this.Invoke(new Func<bool>(() => cbxExceProtocol.Checked));
                bool SqlChecked = (bool)this.Invoke(new Func<bool>(() => cbxSqlProtocol.Checked));
                bool OpcChecked = (bool)this.Invoke(new Func<bool>(() => cbxOpcProtocol.Checked));
                bool CfgChecked = (bool)this.Invoke(new Func<bool>(() => cbxCfgProtocol.Checked));
                bool PmonChecked = (bool)this.Invoke(new Func<bool>(() => cbxPmonProtocol.Checked));
                bool DemChecked = (bool)this.Invoke(new Func<bool>(() => cbxDemProtocol.Checked));

Przecież to będzie strasznie powolne. Invoke jest drogie, a ty nimi tak szastasz w całym kodzie. Przerób kod tak, by idealnie było tylko jedno this.Invoke.
I to nie w ciasnej pętli. takie coś for (i = 0; i < imax; i++) i pod tym Invoke to też bardzo niedobrze.

Co robi Invoke? Czeka aż wątek GUI skończy robić to co robi, wykonuje w nim zadany kod i zwraca wynik. Przez ten cały czas wątek wywołujący Invoke jest blokowany, a wątek GUI jest zamulany twoim kodem. Do tego dochodzi narzut samego Invoke, bo nie jest to zwykłe wywołanie funkcji.

Jeśli robisz Invoke kilka tysięcy razy na sekundę to nie masz żadnego zysku z wielowątkowości.

1 użytkowników online, w tym zalogowanych: 0, gości: 1