How to update a list box by an asynchronous call?
Solution 1
For Win Forms you'll need to use the Control's Invoke method:
Executes the specified delegate on the thread that owns the control's underlying window handle
The basic scenario is:
- Do the heavy lifting work with a BackgroundWorker to retrieve all of your items on a non UI blocking thread.
- On the BackgroundWorker.RunWorkerCompleted Event, use the Control's Invoke method to add the items to the Control (ListBox in your case).
Something along the lines of:
var bw = new BackgroundWorker();
bw.DoWork += (sender, args) => MethodToDoWork;
bw.RunWorkerCompleted += (sender, args) => MethodToUpdateControl;
bw.RunWorkerAsync();
This should get you going in the right direction.
Edit: working sample
public List<string> MyList { get; set; }
private void button1_Click( object sender, EventArgs e )
{
MyList = new List<string>();
var bw = new BackgroundWorker();
bw.DoWork += ( o, args ) => MethodToDoWork();
bw.RunWorkerCompleted += ( o, args ) => MethodToUpdateControl();
bw.RunWorkerAsync();
}
private void MethodToDoWork()
{
for( int i = 0; i < 10; i++ )
{
MyList.Add( string.Format( "item {0}", i ) );
System.Threading.Thread.Sleep( 100 );
}
}
private void MethodToUpdateControl()
{
// since the BackgroundWorker is designed to use
// the form's UI thread on the RunWorkerCompleted
// event, you should just be able to add the items
// to the list box:
listBox1.Items.AddRange( MyList.ToArray() );
// the above should not block the UI, if it does
// due to some other code, then use the ListBox's
// Invoke method:
// listBox1.Invoke( new Action( () => listBox1.Items.AddRange( MyList.ToArray() ) ) );
}
Solution 2
The easiest solution would be to use the BackgroundWorker
control, combined with two Panels
. The idea is to have one panel on the foreground Visible
when the form loads, and have an ImageBox
inside of it that plays a simple loading gif. The ListBox
will be inside the other panel that won't be visible by default and will be right behind the first panel.
Once the form is loaded, start your BackgroundWorker
and accomplish whatever Data retrieving or updating that you have to do and once the Task is complete, set the data inside your ListBox and simply bring the ListBox
panel and make it visible.
That way you'll have a Semi Asynchronous loading of your ListBox
, while it's not updated after every item being added. You can use this technique anytime you want, not simply on form load!
Here is a code example:
namespace AsyncForm
{
public partial class Form1 : Form
{
private List<String> collectionItems = new List<String>();
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 20; i++)
{
((List<String>)e.Argument).Add("Something " + i);
System.Threading.Thread.Sleep(200);
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
listBox1.Items.AddRange(collectionItems.ToArray());
listBox1.Visible = true;
pictureBox1.Visible = false;
}
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(collectionItems);
}
}
}
Solution 3
If you are modifying a UI element, then you are going to HAVE to block the UI thread. If the items come in bursts or require processing between adding each one, then you might want to think about running the processing behind the scenes (via a backgroundworker or a Task). But, if you are just taking data and populating the list, then you are required to use the UI thread.
Solution 4
You should separate function to update UI and long-time process.
To handle UI logic..
private void UpdateUI(string item)
{
if (Thread.CurrentThread.IsBackground)
{
listEvents.Dispatcher.Invoke(new Action(() => //dispatch to UI Thread
{
listEvents.Items.Add(item);
}));
}
else
{
listEvents.Items.Add(item);
}
}
To do asynchronous process using TaskParallel
private void Dowork()
{
Task task = Task.Factory.StartNew(() =>
{
int i = 0;
while (i < 10)
{
Thread.Sleep(1000);
UpdateUI(i.ToString());
i++;
}
});
}
Comments
-
Tharik Kanaka almost 2 years
I have developed a windows forms c# application, i just want update items in a Listbox in the main form by spin offing another thread without blocking the GUI form. Since threads cannot access form entities like listbox, i thought of using delegates. Following code in the below shows how i used a delegate to do that task, but it blocks the GUI form. so i just want to convert it to an asynchronous delegate which updates list box without blocking the GUI Form
delegate declaration
delegate void monitoringServiceDel();
calling the delegate
new monitoringServiceDel(monitoringService).BeginInvoke(null, null);
delegate method implementation
private void monitoringService() { this.listEvents.Invoke(new MethodInvoker(delegate() { int i = 0 ; while (i<50) { listEvents.Items.Add("count :" + count++); Thread.Sleep(1000); i ++; } })); }
-
Tharik Kanaka about 12 yearsat tthen he moment when i add item (listEvents.Items.Add("count :" + count++);) can i have something like asynchrnous call back to update list inside the delegate function ???
-
Justin Pihony about 12 yearsWho creates the panel and list? If the UI, then you would get a context error. However, I guess you could create the panel and list in the backgroundworker and then just pass it up to the UI to be plugged in by the main thread?
-
Alexandre about 12 yearsSorry I deleted the comment. You have the two Panels ready in your Form. You use the BackgroundWorker to get the Data that populate the ListBox. Once the BackgroundWorker is complete and you have all your Data, you can access your controls without issues and crossing threads. So you simply swap the panel that's visible and set the ListBox content.
-
Justin Pihony about 12 years@PeekaySwitch How is that different from what I said (beyond being more explicit?) Once you have the data, you have to block the UI thread?
-
Alexandre about 12 years@JustinPihony I wouldn't exactly call it blocking the UI Thread. The BackgroundWorker leaves the UI Responsive and the Panel Visibility swap + ListBox populating will take 0.001 second to accomplish and won't freeze the UI.
-
Justin Pihony about 12 years@PeekaySwitch Yes, and for the example given it is a moot point, which is what I was saying. I will leave it up to the OP on this one
-
Tharik Kanaka about 12 yearsI have included your coding in a button click event. It will invoke MethodToDoWork successully and once it tries to update the listbox inside the MethodToUpdateControl, it will generate an exception by saying "TargetInvocationException was unhandled" at Application.Run(new FormMain()); in program.cs
-
nishantvodoo about 8 yearssaved my life. Works perfect
-
John Lord over 2 yearsworked for me if i removed "dispatcher". I didn't have that as a method