How to update a list box by an asynchronous call?

Go To StackoverFlow.com

3

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 ++;
            }

        }));


}
2012-04-04 03:41
by Tharik Kanaka


7

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:

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() ) ) );
}
2012-04-04 04:48
by Metro Smurf
I 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.c - Tharik Kanaka 2012-04-04 06:40
See updated with working code example - Metro Smurf 2012-04-04 14:38
Thanks a lot its working : - Tharik Kanaka 2012-04-05 08:00
saved my life. Works perfec - nishantvodoo 2016-04-02 05:44


2

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.

2012-04-04 03:49
by Justin Pihony
at 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 ?? - Tharik Kanaka 2012-04-04 03:56
Who 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 - Justin Pihony 2012-04-04 04:09
Sorry 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 - Alexandre 2012-04-04 04:15
@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 - Justin Pihony 2012-04-04 04:18
@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 - Alexandre 2012-04-04 04:22
@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 on - Justin Pihony 2012-04-04 04:37


1

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);
        }
    }
}
2012-04-04 04:09
by Alexandre


0

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++;
            }
        });
    }
2012-04-04 05:32
by aifarfa
Ads