I am trying to use a background worker in order to retrieve a large amount of data from the database without stalling the main thread. This seems to work well, except that when it comes to update the UI, the update freezes the screen. Relevant code as follows:
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lvwTest.BeginUpdate();
lvwTest.Items.Clear();
// Populate the UI
foreach (TestItem ti in testData)
{
ListViewItem lvi = lvwTest.Items.Add(ti.Value1);
lvi.SubItems.Add(ti.Value2);
}
lvwTest.EndUpdate();
}
The update takes around 2 - 3 seconds, for which time the screen is locked. I realise that only the main thread can update the screen, but is it possible to load this data into memory in some way (in a background thread or another instance of a listview or something) and then just display it? All I want to happen is for the program to simply refresh the data without taking up time in the main thread.
I recommend loading the data into memory and using a virtual mode ListView. That way, you only create the ListViewItem objects as they are needed.
If you have to load really huge amount of data into UI it will require time and it will block our app. The option is smart scrolling or pagination. You load all data but you put it piece by piece upon user request.
Since most of the above are good advice, but don't really solve your immediate problem, here is another approach:
This will update your GUI and keep it responsive. Assuming you are in WinForm App?
Application.DoEvents();
this.Refresh();
Nevertheless, this does not mean that maybe you should not listen to the ideas from above :-)
In addition to virtualization, I would recommend breaking the items into batches of, say, 100 and adding each batch in its own message. That way, the UI has a change to process other messages whilst the batches are being added to the ListView.
In other words, all the RunWorkerCompleted handler does is queue the first batch for adding in a separate message. The adding method will then add the items and then queue the next batch. This will continue until there are not more items left to add. At that point, you would re-enable the relevant portion of your UI (the ListView).
Related
EDIT: Solved using this: http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/
In my project (.net/windows forms) I'm filling a DataGridView with a large DataTable. Filling can take up to 20 seconds, so I'd like an animated loading window. This animation freezes if the thread is busy, so I'll have to use a new thread either for the window or for filling the DataGridView.
I've tried using the BackgroundWorker to show the form, but it'll be a blank white window in the correct shape.
I've also tried using the BackgroundWorker to fill the DataGridView, but it'll throw an error saying the DataGridView is being accessed by a different thread than the one it has been created for. Since the DataGridView is being created in the designer class, I can't just create it in the new thread - plus that solution doesn't sound very elegant.
What's the best way for me to show a working animated form while filling the DataGridView?
Edit: The answer didn't solve the issue for me, so I've tried to break the code down to something that can be presented here. I didn't do it before because it didn't really seem relevant enough to work through some 1k lines of code. Might be something missing or some remnants from previous experiments in the code presented here. Please ignore bad naming of function, that's a legacy thing I'll fix once I got it working. Parts of the code are ancient.
I've made it run without errors, but the frmLoading still isn't animated (it is if I keep it alive while the working thread isn't busy, though).
namespace a
{
public partial class frmMain : DockContent, IPlugin
{
//...
private delegate void SafeCallDelegate(DataTable dt);
private Thread thread1 = null;
private frmLoading frmLoading = new frmLoading();
public frmMain()
{
//...
}
//...
private void FillDataGrid(DataTable dt)
{
if(this.InvokeRequired)
{
var d = new SafeCallDelegate(FillDataGrid);
Invoke(d, new object[] { dt });
}
else
{
//...
DataGridFiller(dt);
}
}
private void DataGridFiller(DataTable dt)
{
BindingSource dataSource = new BindingSource(dt, null);
//...
dgvData.DataSource = dataSource;
//...
frmLoading.Hide();
}
private void btnGetData_Click(object sender, EventArgs e)
{
DataTable dt = [...];
// Wenn Daten vorhanden sind, dann anzeigen
if (dt != null)
{
//...
frmLoading.Show();
thread1 = new Thread(() => FillDataGrid(dt));
thread1.Start();
}
}
}
}
The second approach is the correct one: use a BackgroundWorker to do any work that will freeze the UI if done in the main thread. About the exception you're getting, that's because the call you're making isn't thread-safe (Because the control was created on a different thread that the one whos calling its methods). Plase take a look at this link to MSDN to understand how to make this kind of call between threads, using backgroundWorker's event-driven model.
From the link:
There are two ways to safely call a Windows Forms control from a
thread that didn't create that control. You can use the
System.Windows.Forms.Control.Invoke method to call a delegate created
in the main thread, which in turn calls the control. Or, you can
implement a System.ComponentModel.BackgroundWorker, which uses an
event-driven model to separate work done in the background thread from
reporting on the results.
Take a look at the second example which demonstrates this technique using the backgroundWorker
EDIT
From your comments I get that the real problem here is size. The problem is that the thread that owns the DataGridView control, is the thread that is displaying it, so no matter how, if you load all the data at once it will freeze for the time it takes this thread to draw all that data on the screen. Fortunately you're not the first that had this problem, and microsoft got you covered this time. This is, I think, a great example of what the XY problem is, the real problem is that you want to load a huge dataset (X) and the answer on how to do that is here, but instead you asked how to display a loading icon while filling your datagrid whithout the UI freezing (Y), which was your attempted solution.
This was from a technical perspective but, from an UI/UX perspective I would like you to think about the actuall use of the form. Do you really need all that data loaded every time you visit this section? If the answer is no, maybe you could think about implementing some pagination (example here). Also 200 colums means horizontal scroll even for an ultrawide monitor. I can't really figure out the user case for you to need all that information on a list/table view at once. I think that maybe implemeting a Master-Detail kind of interface could be more useful.
EDIT 2.0
I think that you can try to create a whole new Form window in another thread, place it over your datagrid and paint the loading animation there. Meanwhile in the main window you can draw the grid without its painting making the loading animation freeze (the other thread is taking care of the drawing). You may want to hold a reference to the thread and kill it, or maybe better try to hold a reference to the From and close it more gracefully.
I've never done this but, I think I recall from some legacy application doing something like that, and it was ugly.
Use an asynchronous task that displays the pop-up loading window until the DataGridView is filled up.
Here's a link to a write-up Microsoft made for async programming: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
In my form_load I have a mysql connection that loads data in a datagrid, when starting my application it will first try to load the mysql database and after its done it will load the form/interface with all the controls.
This leaves the user the impression nothing is happening, how can I first load the form/interface with all the controls and then my mysql database?
I would say, 3 approaches
1 - Separate Splash screen
2 - On-Form "splash screen" - first thing you do on form.load, is Me.Show(), then show a message, something like "Please wait while data is loaded". Progress can be shown to users as well using Application.DoEvents which will refresh your screen.
3 - Background worker thread. Using it, you can actually give user progress updates. That will leave your form completely responsive. http://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx
Move your code to Shown event or OnShown overridden method. this will allow your form shown to user before it loads grid.
You should move your "Database calls" to worker threads as well.
protected override void OnShown(EventArgs e)
{
//Delegate the DB calls to worker threads here
//everything will be smooth then..
base.OnShown(e);
}
There are two ways to approach this situation. You need to pick the approach that best suits your needs.
Approach 1: Create a splash screen. A splash screen is a small window that opens up and usually had product info and a message like "Initializing...". You can see this in a lot of applications, such as Microsoft Office. You can see how to make one by looking at this article.
Approach 2: You will need to make your load data happen in a separate thread then your UI. You will need to load your data in the separate thread and bind it to a list of some sort temporarily, then after its done, bind the list to your Datagrid
Use other thread for loading data from mysql. It's loading form first, but not displaying it until code has finished work.
new Thread(new ThreadStart(LoadDataMethodHere)).Start();
You can use Form Shown event.
Alternatively you can use a different thread to connect to database and show data or use a timer.
I have the follow button click event:
private void btnRun_Click(object sender, EventArgs e)
{
label1.Visible = true;
if (SelectDatabase())
{
if (string.IsNullOrEmpty(txtFolderAddress.Text))
MessageBox.Show("Please select a folder to begin the search.");
else
{
if (cbRecurse.Checked == false || Directory.GetDirectories(initialDirectory).Length == 0)
{
CheckSingleFolder();
}
else
{
CheckSingleFolder();
directoryRecurse(initialDirectory);
}
}
}
}
Effectively, it does a few checks and then starts some directory recursion looking for specific files. However, the very first line of code to make the label visible does not occur until after the directories have been recursed? Anybody know why this would be?
Thanks.
You're doing everything within the UI thread, which is a really bad idea - the UI doesn't get to update, react to events etc until you've finished.
You should use a background thread and update the UI with progress etc using Control.BeginInvoke, or perhaps use a BackgroundWorker.
Basically, there are two golden rules in WinForms (and similar with WPF/Silverlight):
Don't do anything which can take a significant amount of time in the UI thread
Don't touch any UI elements from any thread other than the UI thread
your whole method runs as a blocking unit currently - add an Application.DoEvents() as a workaround, but really you should be doing this kind of processing in a background thread, i.e. using a background worker.
The code is executing on the same thread which is drawing your user-interface. Therefore, while the code is executing, your UI is not being re-drawn. Once the button-click code has finished, the UI is redrawn and label1 gets drawn invisibly.
You can move your code into a separate thread using, for example, Task or BackgroundWorker. However, you cannot directly set UI properties from a different thread, so you will need to either be careful to set your UI properties from the UI thread or see this question about how to update the GUI from another thread.
The view is not updated until the code block finished. So I would propose a BackgroundWorker for the recursion part.
The explanation: the label is set to visible and it it is Invalidated (needs repainting) but the Windows message pump doesn't start repainting until it is running idle. So your code blocks it.
A simple solution is to call label1.Update() right after setting it visible.
A better solution is to move the time-consuming code to a thread (Backgroundworker).
I have a form that is loading quite an amount of data from SQL server. Below is the code that will provide a good hint:
private void BranchCenter_Load(object sender, EventArgs e) {
//Combo boxes:
LoadCities();
LoadCoordinators();
LoadComputerSystems();
//Actual entity
LoadBranch();
}
private void LoadCities() {
//LINQ2SQL to load data. ~5000 records.
}
private void LoadCoordinators() {
//LINQ2SQL to load data. ~50 records.
}
private void LoadComputerSystems() {
//LINQ2SQL to load data. ~550 records.
}
private void LoadBranch() {
LoadBranchInit();
LoadBranchDetails();
LoadBranchTimings();
LoadBranchServices();
LoadBranchLocumsHistory();
LoadBranchJobs();
LoadBranchNotes();
}
private void LoadBranchInit() {
//LINQ2SQL to load the Branch object based upon the Branch ID
}
private void LoadBranchDetails() {
//LINQ2SQL to load basic branch stuff. 38 fields. Mixed editors.
}
private void LoadBranchTimings() {
//LINQ2SQL to load timings info into 80 date-time controls
}
private void LoadBranchServices() {
//LINQ2SQL to load services offered at branch info into 20 check-boxes controls
}
private void LoadBranchLocumsHistory() {
//LINQ2SQL to load branch history info into grid control. Always increasing # of rows :(
}
private void LoadBranchJobs() {
//LINQ2SQL to load branch jobs info into grid control. Always increasing # of rows :(
}
private void LoadBranchNotes() {
//LINQ2SQL to load branch notes info into grid control
}
The UI is a form with a tab controls. each detail from above goes to a tab page. I need to load and show the form to user as fast as possible. once the form is shown, i need to launch a series of background workers to get the data for each page.
I have been trying to mess with the background worker but unable to understand it's usage. I end up getting message of "different thread attempted to access control on your main thread... or something like that..."
The ideal situation would be to have a progress bar loading the data on every tab and the tab becoming inter-actable once the respective background worker finishes.
Any strategy or advice? Thanks for the read.
If each type of data is displayed on just one page, I would move the code so that each corresponding type of data is loaded the first time each tab page is loaded. That way the data loading work will be distributed over a longer time, and perhaps avoided altogether if the user does not navigate to all tabs during a session.
When loading a page, you could use a BackgroundWorker or any other asynchronous mechanism. In the tab page you can have two panel controls. One that contains the UI for the page, and that has Visible = false when the form is loaded, and one containing a label with a text like "Loading, please wait...". When the data is loaded for the page, and the UI is updated, toggle visibility on the two panels to show the UI. That way the form will still be responsive while the data is loading, and since you load data for only one page at a time, load times should be fairly short.
the ideal solution is that you do not really load data in the form load event.
Let the form load properly and entirely so the UI render completes.
After that you could display a progress bar wherever you want and you could execute the actual data loading, either synchronous or asynchronous.
if you don't need everything loaded at the same time I would also consider loading data only the first time a tab is accessed, so if the user never clicks on the last tab, for example, you didn't load anything for it.
Create a BackgroundWorker for each of the tabs. In the Load event of the form, disable all tabs and call the RunWorkerAsync() method for each of the background workers.
In each DoWork event handler, load the data required for the associated tab page from the database into a data table and set the Result property of the DoWorkEventArgs to the data table.
Note: In the DoWork event handler you should NOT update any UI control since it is operating in a different thread. You should only retrieve the data from the database and set the Result property.
In the RunWorkerCompleted event handlers, you can access the data table, that was retrieved in the DoWork event handler, by getting the Result property of the RunWorkerCompletedEventArgs. Then you can set the properties of the UI controls accordingly and then enable the tab page associated with the current background worker.
The problem here is that the DoWork method of the BackgroundWorker cannot touch the GUI.
It has to use the BackgroundWorker.ReportProgress( int progress, object state) to invoke a method on the GUI thread that is free to update the GUI.
The important point here is that the GUI controls should only be updated from the GUI thread itself, else random exceptions will occur here and there in your program.
You can't mess around with the UI from any other thread apart from the UI thread; that's the source of your errors. If you want more information on how to properly update a UI element from a different thread, you'll need to use Invoke. This question on Stack Overflow might help you out.
Yes, BackgroundWorker is great for this.
I think that you do it wrong because you want to change your form in the DoWork event.
However, you should only collect results of this in e.Results of that event
Then in RunWorkerCompleted you use its e.Results to change the form.
The TabControl already does lazy-loading.
Do yourself a favor and place the contents of each tab on a UserControl.
To address just the problem you are encountering with the background worker thread, it sounds like you may be trying to access UI components from the thread itself. To do this from a non-UI thread you will need to make use of Control.Invoke rather than trying to access the components directly.
Okay, I face a new scenario. I shifted to Visual Studio 2010 and ran into Task.Factory.StartNew(() => myMethod());
This is what I have so far:
private void LoadLocum() {
var TaskInit = Task.Factory.StartNew(() => LoadLocumInit());
TaskInit.Wait();
var TaskDetails = Task.Factory.StartNew(() => LoadLocumDetails());
TaskDetails.Wait();
var TaskQualifications = Task.Factory.StartNew(() => LoadLocumQualifications());
//Enable Qualification TabPage automatically whenever TaskQualifications is complete
Parallel.Invoke(
() => LoadLocumComputerSystems(),
() => LoadLocumOtherInfo(),
() => LoadLocumEmergencyContacts(),
() => LoadLocumDistanceRates(),
() => LoadLocumDocuments(),
() => LoadLocumNotes(),
() => LoadLocumBannedStatus()
);
}
The first two steps (tasks) are critical. Now, the Tab Pages of the Tab are disabled. I need to enable them based upon their related task completion. I can find an event to subscribe to that indicates a certain task is completed or not.
Okay. Massive response in just about no time. Love this place. Jumping from link-2-link, I came face to face with Reactive Extensions for .NET (Rx). Can this be a good alternate as well? Can't find a tutorial on how to use Rx in my scenario.
I've got a Windows Forms (C#) project with multiple comboboxes/listboxes etc that are populated when the form loads.
The problem is that the loading of the comboboxes/listboxes is slow, and since the loading is done when the form is trying to display the entire form isn't shown until all the controls have been populated. This can in some circumstances be 20+ seconds.
Had there been a Form_finished_loaded type of event I could have put my code in there, but I can't find an event that is fired after the form is done drawing the basic controls.
I have one requirement though - the loading has to be done in the main thread (since I get the items from a non-threading friendly COM-application).
I have found one potential solution, but perhaps there is a better way?
I can create a System.Timer.Timer when creating the form, and have the first Tick be called about 1 second later, and then populate the lists from that tick. That gives the form enough time to be displayed before it starts filling the lists.
Does anyone have any other tips on how to delay the loading of the controls?
There is the Shown event that "occurs whenever the form is first displayed.". Also you may want to use the BeginUpdate and EndUpdate functions to make the populating of your combobox faster.
It has that certain smell of workaround, but this approach should fulfil your needs:
private bool _hasInitialized = false;
private void Form1_Shown(object sender, EventArgs e)
{
if (!_hasInitialized)
{
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(200); // brief sleep to allow the main thread
// to paint the form nicely
this.Invoke((Action)delegate { LoadData(); });
});
}
}
private void LoadData()
{
// do the data loading
_hasInitialized = true;
}
What it does is that it reacts when the form is shown, checks if it has already been initialized before, and if not it spawns a thread that will wait for a brief moment before calling the LoadData method on the main thread. This will allow for the form to get painted properly. The samething could perhaps be achieve by simply calling this.Refresh() but I like the idea of letting the system decide how to do the work.
I would still try to push the data loading onto a worker thread, invoking back on the main thread for populating the UI (if it is at all possible with the COM component).
Can you get your data from a web service that calls the COM component?
That way, you can display empty controls on a Locked form at the start, make Asynchronous calls to get the data, and on return populate the respective combos, and once all of them are loaded, you can unlock the form for the user to use.
You could listen for the VisibleChanged event and the first time it's value is true you put your initialization code.
Isn't FormShown the event you're looking for?
When you say that you cannot use a background thread because of COM what do you mean? I am using many COM components within my apps and running them on background threads.
If you create a new thread as an STAThread you can probably load the ComboBox/ListBox on a Non-UI thread. IIRC the ThreadPool allocates worker threads as MTAThread so you'll need to actually create a thread manually instead of using ThreadPool.QueueUserWorkItem.