2

I have a .NET 4.5 WinForm program that queries a text-based database using ODBC. I then want to display every result in a multiline textbox and I want to do it in the quickest way possible.
The GUI does not have to be usable during the time the textbox is being updated/populated. However, it'd be nice if I could update a progress bar to let the user know that something is happening - I believe a background worker or new thread/task is necessary for this but I've never implemented one.

I initially went with this code and it was slow, as it drew out the result every line before continuing to the next one.

OdbcDataReader dbReader = com.ExecuteReader();
while (dbReader.Read())
{
   txtDatabaseResults.AppendText(dbReader[0].ToString());
}

This was significantly faster.

string resultString = "";
while (dbReader.Read())
{
   resultString += dbReader[0].ToString();
}
txtDatabaseResults.Text = resultString;

But there is a generous wait time before the textbox comes to life so I want to know if the operation can be even faster. Right now I'm fetching about 7,000 lines from the file and I don't think it's necessary to switch to AvalonEdit (correct me if my way of thinking is wrong, but I would like to keep it simple and use the built-in textbox).

valsidalv
  • 761
  • 2
  • 19
  • 33
  • 1
    You might be interested in [My Example](http://stackoverflow.com/a/16745054/643085) of a similar thing using current, relevant .Net Windows UI technologies. – Federico Berasategui Oct 15 '13 at 17:34
  • 1
    Just a note on your comment about background worker or new thread/task, background workers are *insanely* easy to work with. I just learned of them the other week and have fallen in love. dotnetperls has a great, simple example on how to use one: http://www.dotnetperls.com/progressbar Except instead of passing i to the ReportProgress method, you'd have to perform some basic math to figure out what percentage your task is at, such as current row divided by total rows in result set. – sab669 Oct 15 '13 at 17:42
  • @HighCore great link and answer, but may be too much power for my current application. – valsidalv Oct 15 '13 at 17:47
  • @valsidalv it's a regular .Net 4.0 solution, it's even older than the current .Net version you're using. I don't see how that is "too much power". The difference is I'm making use of the correct classes inside .Net (WPF) instead of the deprecated ones. – Federico Berasategui Oct 15 '13 at 17:50
  • Winforms still also suppport ancient win32 'owner drawn' functionality, which effectively lets you virtualize the listbox contents. See: http://msdn.microsoft.com/en-us/library/ms229679(v=vs.90).aspx –  Oct 15 '13 at 17:55
  • @HighCore it's overkill for my current app, which is still in proof-of-concept phase. Depending on how things pan out I may use it later. – valsidalv Oct 15 '13 at 17:56
  • 1
    @valsidalv in the `WPF world`, that is just a `trivial thing`, not so powerful as you think. – King King Oct 15 '13 at 18:05

4 Answers4

6

You can make this far faster by using a StringBuilder instead of using string concatenation.

var results = new StringBuilder();
while (dbReader.Read())
{
    results.Append(dbReader[0].ToString());
}
txtDatabaseResults.Text = results.ToString();

Using string and concatenation creates a lot of pressure on the GC, especially if you're appending 7000 lines of text. Each time you use string +=, the CLR creates a new string instance, which means the older one (which is progressively larger and larger) needs to be garbage collected. StringBuilder avoids that issue.

Note that there will still be a delay when you assign the text to the TextBox, as it needs to refresh and display that text. The TextBox control isn't optimized for that amount of text, so that may be a bottleneck.

As for pushing this into a background thread - since you're using .NET 4.5, you could use the new async support to handle this. This would work via marking the method containing this code as async, and using code such as:

string resultString = await Task.Run(()=>
{
    var results = new StringBuilder();
    while (dbReader.Read())
    {
        results.Append(dbReader[0].ToString());
    }
    return results.ToString();
});

txtDatabaseResults.Text = resultString;
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Not really, the limitation sometimes **depends on** the `TextBox` itself, all the string is already in memory but it takes the `TextBox` much time to render the `Text`. – King King Oct 15 '13 at 17:34
  • 1
    @KingKing With 7000 lines, you're almost guaranteeing GC pressure. That's going to cause a slowdown - though granted, the `TextBox` may still be the largest factor. – Reed Copsey Oct 15 '13 at 17:35
  • That helped a lot. Does StringBuilder affect the way the textbox draws out the text, or is it simply quicker at building the string? – valsidalv Oct 15 '13 at 17:37
  • @valsidalv Just quicker building the string - I put in another potential optimization for you... If that doesn't help, though, the next step would be a better control. – Reed Copsey Oct 15 '13 at 17:38
  • I am now wondering (in the async example) where the code to update my progress bar would go. I already have the total number of lines in the file, but I need to get the current line - an `int` that'd be incremented right after `results.Append()`. – valsidalv Oct 15 '13 at 20:57
  • @valsidalv It gets a bit trickier if you want progress. The normal way to do that is via IProgress and the Progress class... – Reed Copsey Oct 15 '13 at 20:58
3

Use a StringBuilder:

StringBuilder e = new StringBuilder();
while (dbReader.Read())
{
   e.Append(dbReader[0].ToString());
}
txtDatabaseResults.Text = e.ToString();
Daniel
  • 10,864
  • 22
  • 84
  • 115
1

Despite the fact that a parallel Thread is recommended, the way you extract the lines from file is somehow flawed. While string is immutable everytime you concatenate resulString you actually create another (bigger) string. Here, StringBuilder comes in very useful:

StringBuilder resultString = new StringBuilder ()
while (dbReader.Read())
{
   resultString = resultString.Append(dbReader[0].ToString());
}
txtDatabaseResults.Text = resultString;
Alireza
  • 10,237
  • 6
  • 43
  • 59
1

I am filling a regular TextBox (multiline=true) in a single call with a very long string (more than 200kB, loaded from a file. I just assign the Text property of TextBox with my string). It's very slow (> 1 second). The Textbox does anything else than display the huge string.

I used a very simple trick to improve performances : I replaced the multiline textbox by a RichTextBox (native control).

Now same loadings are instantaneous and RichTextBox has exactly the same appearance and behavior as TextBox with raw text (as long as you didn't tweaked it). The most obvious difference is RTB does not have Context menu by default.

Of course, it's not a solution in every case, and it's not aiming the OP question but for me it works perfectly, so I hope it could help other peoples facing same problems with Textbox and performance with big strings.

AFract
  • 8,868
  • 6
  • 48
  • 70