How to update a list box by an asynchronous call?

17,807

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:

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++;
            }
        });
    }
Share:
17,807
Tharik Kanaka
Author by

Tharik Kanaka

Full Stack Developer and a Life Hacker =)

Updated on June 29, 2022

Comments

  • Tharik Kanaka
    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
    Tharik Kanaka about 12 years
    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 ???
  • Justin Pihony
    Justin Pihony about 12 years
    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?
  • Alexandre
    Alexandre about 12 years
    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.
  • Justin Pihony
    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
    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
    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
    Tharik Kanaka about 12 years
    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.cs
  • nishantvodoo
    nishantvodoo about 8 years
    saved my life. Works perfect
  • John Lord
    John Lord over 2 years
    worked for me if i removed "dispatcher". I didn't have that as a method