0

I was trying to solve the problem in this Question but I ended up having another problem
in short words that question was asking how to load a huge file into textBox chunk by chunk,
so in back ground worker Do_work event I did this:

using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
{
    int bufferSize = 50;
    byte[] c = null;        
    while (fs.Length - fs.Position > 0)
    {
        c = new byte[bufferSize];
        fs.Read(c , 0,c.Length);                    
        richTextBox1.AppendText(new string(UnicodeEncoding.ASCII.GetChars(c)));
    }                
}        

that didn't work because a backgroundWorker can't affect UI elements and I need to use BeginInvoke to do it.

so I changed the code:

delegate void AddTextInvoker();

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;            
        while (fs.Length - fs.Position > 0)
        {
            c = new byte[bufferSize];
            fs.Read(c , 0,c.Length);                    
            richTextBox1.AppendText(new string(UnicodeEncoding.ASCII.GetChars(c)));
        }                
     }           
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    this.BeginInvoke(new AddTextInvoker(AddText));
}

there are two problems with this code.
1- it's taking longer and longer time to append the text (I think because of string immutability replacing the text over time will take longer)
2- on every addition the richTextBox will scroll down to the end which causing application hang.

the question is what can I do to stop the scrolling and application hang?
and what can I do to enhance string concatenation here?

Edit: after some testing and using Matt's answer I got this:

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;             
        while (fs.Length - fs.Position > 0)
        {
           c = new byte[bufferSize];
           fs.Read(c , 0,c.Length);

           string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
           this.BeginInvoke((Action)(() => richTextBox1.AppendText(newText)));

           Thread.Sleep(5000); // here 
         }                
     }        
 }

when the loading pauses I can read and write without problems or hanging, once the text exceeded the the richTextBox size the loading will scroll down and will prevent me from continue.

Community
  • 1
  • 1
Alaa Jabre
  • 1,843
  • 5
  • 26
  • 52

5 Answers5

1

One problem I see is that your background worker is, well, not doing any work in the background. It's all running on the UI thread. This may be why the UI thread is non-responsive.

I would refine your DoWork handler like so:

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", 
                   FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;            
        while (fs.Length - fs.Position > 0)
        {
            c = new byte[bufferSize];
            fs.Read(c , 0,c.Length);

            string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
            this.BeginInvoke((Action)(() => richTextBox1.AppendText(newText));
        }                
     }           
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    AddText();
}

What I've done is localized the use of BeginInvoke to the single UI call made in the handler. That way, all of the other work is done in the background thread. Maybe that will help with the UI thread becoming non-responsive.

Matt Dillard
  • 14,677
  • 7
  • 51
  • 61
  • Suggested improvement: preload X number of lines and load more when the textbox scrolls. – Dustin Kingen Aug 08 '13 at 01:30
  • I think this also doesn't work, there must be many `richTextBox1.AppendText(...)` being run nearly simultaneously and that may cause non-responsiveness on the `richTextBox1` – King King Aug 08 '13 at 01:32
  • @KingKing yes it didn't, I'm trying to find out if there is a way to stop the auto scroll down – Alaa Jabre Aug 08 '13 at 01:33
  • @Star you may want to try calling `richTextBox1.SuspendLayout()` before the `while` and calling `richTextBox1.ResumeLayout(true)` after the `while`. – King King Aug 08 '13 at 01:35
  • @KingKing I'm positive now that the hang reason is the scrolling down, I will edit the question. – Alaa Jabre Aug 08 '13 at 01:40
1

Just call Application.DoEvents. That's the simplest thing, no need to worry about manually creating or synchronizing threads or background workers, yet your app stays responsive.

Also, try using File.ReadLines, which is a lazy-loaded enumerable, rather than manually using a FileStream. This, for example, works for me, and gives you everything you need in a loop and two lines of code.

private void button1_Click(object sender, EventArgs e)
{
    foreach (var line in File.ReadLines(@"C:\Users\Dax\AppData\Local\Temp\dd_VSMsiLog0D85.txt", Encoding.ASCII))
    {
        richTextBox1.AppendText(line + "\r\n");
        Application.DoEvents();
    }
}

Alternately you can specify your chunk size and load it by that. This will run a bit faster, but take a bit longer (less than a second though) to read the full file at first.

private void button1_Click(object sender, EventArgs e)
{
    var text = File.ReadAllText(@"C:\Users\Dax\AppData\Local\Temp\dd_VSMsiLog0D85.txt", Encoding.ASCII);
    const int chunkSize = 1000000;
    for (var i = 0; i < text.Length / chunkSize; ++i)
    {
        richTextBox1.AppendText(text.Substring(chunkSize * i, chunkSize));
        Application.DoEvents();
    }
}

Try this third option and see if your hang is caused by the file or by the loop:

void button1_Click(object sender, EventArgs e)
{
    while(true)
    {
        richTextBox1.AppendText("a");
        Application.DoEvents();
    }
}
Dax Fohl
  • 10,654
  • 6
  • 46
  • 90
  • I tried both way and the Two of theme caused application hang, am I missing something?? – Alaa Jabre Aug 08 '13 at 06:05
  • @Star I don't know, both work for me. I just created a new winforms project, added a richtextbox and a button, and added this as the button's click handler. I gave you a third option to try in my answer. – Dax Fohl Aug 08 '13 at 11:58
  • I didn't, anyway I don't agree with him :) – Alaa Jabre Aug 09 '13 at 13:11
0

Ok that's not exactly the best solution but it do what I want, instead of using AppendText which will surly scroll down I used +=, and I still got the hang so needed the Sleep(100)

public void AddText()
{            
    using (FileStream fs = new FileStream(@"myFilePath.txt", FileMode.Open, FileAccess.Read))
    {
        int bufferSize = 50; 
        byte[] c = null;             
        while (fs.Length - fs.Position > 0)
        {
            c = new byte[bufferSize];
            fs.Read(c , 0,c.Length);

            string newText = new string(UnicodeEncoding.ASCII.GetChars(c));
            this.BeginInvoke((Action)(() => richTextBox1.Text += newText));

            Thread.Sleep(100);
         }                
    }        
}

this will actually not allow me to scroll down until loading is done, I couldn't come up with a better idea.

Edit: And a work around to be able to read the text before loading is done is to set scrollBasrs on richTextBox to None and set the form autoscroll to true and in TextChanged event:

private void richTextBox1_TextChanged(object sender, EventArgs e)
        {
            using (Graphics g = CreateGraphics())
            {
                SizeF size = g.MeasureString(richTextBox1.Text, richTextBox1.Font);
                richTextBox1.Width = (int)Math.Ceiling(size.Width) > 
                    richTextBox1.Width ? (int)Math.Ceiling(size.Width) : richTextBox1.Width;
                richTextBox1.Height = (int)Math.Ceiling(size.Height) >
                    richTextBox1.Height ? (int)Math.Ceiling(size.Height) : richTextBox1.Height;
            }            
        }

this way I will be able to scroll down while loading. I hope someone find a better solution.

Alaa Jabre
  • 1,843
  • 5
  • 26
  • 52
  • Oh yes, this slows down the speed of `reading` and that's why the `non-responsiveness` is eliminated. Have you tried my suggestion about using `SuspendLayout` and `ResumeLayout`? – King King Aug 08 '13 at 02:06
  • @KingKing I did, but I didn't notice any difference. – Alaa Jabre Aug 08 '13 at 02:09
0

Try this:

private void button1_Click(object sender, EventArgs e)
{
    backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    using (FileStream fs = new FileStream(
        @"myFilePath.txt", FileMode.Open, FileAccess.Read))
        {
            int bufferSize = 50;
            byte[] c = null;
            while (fs.Length - fs.Position > 0)
            {
                c = new byte[bufferSize];
                fs.Read(c, 0, c.Length);
                Invoke(new Action(() =>
                    richTextBox1.AppendText(
                        new string(UnicodeEncoding.ASCII.GetChars(c)))));
            }
        }
    }

The problem was the BeginInvoke, that call the AppendText async, see http://msdn.microsoft.com/en-us/library/0b1bf3y3.aspxhttp://msdn.microsoft.com/en-us/library/0b1bf3y3.aspx and http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

Keep in mind that the Invoke method can throw exception, eg: if you close the form while loading text, so put it in try catch block and handle it.

Alessandro D'Andria
  • 8,663
  • 2
  • 36
  • 32
0

BackgroundWorker CAN affect elements on the UI thread, since its events 'ProgressChanged' and 'RunWorkerCompleted' are executed on the calling thread. The following example increments an integer variable up to 10 on a separate thread, and communicates each increment back to the UI thread.

BackgroundWorker _worker;

private void button1_Click(object sender, EventArgs e)
{
    _worker.RunWorkerAsync();
}

private void Form1_Load(object sender, EventArgs e)
{
    _worker = new BackgroundWorker();
    _worker.WorkerReportsProgress = true;
    _worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    _worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = (BackgroundWorker)sender;

    for (int i = 0; i < 10; i++)
    {
        // pass value in parameter userState (2nd parameter), since it can hold objects
        worker.ReportProgress(0, i); // calls ProgressChanged on main thread
    }
}

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // get value, passed in DoWork, back from UserState
    richTextBox1.AppendText(e.UserState.ToString());
}
Kai Hartmann
  • 3,106
  • 1
  • 31
  • 45