简体   繁体   中英

Multi-Threading pauses form

I am doing some MultiThreading but the form pauses on load.

I am trying to display the form, and then in the background it should populate the combobox without pausing the form.

On my Form_Load event, I have this:

private void frmIni_Load(object sender, EventArgs e)
{
      Application.DoEvents();
      Thread threadOne = new Thread(GetServers);
      threadOne.Start();
}

In my GetServers() method:

private void GetServers()
{
       cboServer.BeginInvoke(
          (Action)(() => {
              servers = SmoApplication.EnumAvailableSqlServers(false);
              Thread.Sleep(1);
              foreach (DataRow server in servers.Rows)
              {
                  cboServer.Properties.Items.Add(server["Name"]);
                  Thread.Sleep(1);
              }
        }));
}

What am I missing here? The form should not pause, it should work and then eventually when the thread completes, it should just populate the combobox.

Yeah so the reason it blocks the UI is simply because no real code is running in the new thread. In your call to GetServers you call back into the UI thread and then do the busy stuff (you may as well not use a thread at all here...).

You want to put any long-running work in the thread and only callback into the UI thread when you want to update it eg

private void frmIni_Load(object sender, EventArgs e)
{
    Task.Factory.StartNew(() => {
        return SmoApplication.EnumAvailableServers(false);
    }).ContinueWith((task) => {
        foreach (var server in task.Result)
        {
            cboServer.Properties.Items.Add(server["Name"]);
        }
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

Points to note

  • Do not use DoEvents when trying to multi-thread, especially when you don't understand it's usage .
  • Do not spin up new threads unless you are sure you need to (eg long running I/O task)
  • Do not use Thread.Sleep to simulate "pausing" (which you appear to be doing)
  • Do use the thread-pool for short-lived work (like this).

Because of the BeginInvoke , all of your code is executed on the UI thread.

private void GetServers()
{
    servers = SmoApplication.EnumAvailableSqlServers(false);
    Thread.Sleep(1); // Why?
    cboServer.BeginInvoke(
            (Action)(() => {
                foreach (DataRow server in servers.Rows)
                {
                    cboServer.Properties.Items.Add(server["Name"]);
                    Thread.Sleep(1); // Why?
                }
            })
        );
}

First: creating a Thread will consume a small amount of time. Also, you are dispatching your actual work back to the UI with your BeginInvoke

cboServer.BeginInvoke(
    (Action)(() => {
        servers = SmoApplication.EnumAvailableSqlServers(false);
        Thread.Sleep(1);
        foreach (DataRow server in servers.Rows)
        {
            cboServer.Properties.Items.Add(server["Name"]);
            Thread.Sleep(1);
        }
    })
);

You should try to move everything besides UI updates out of the BeginInvoke delegate, somehow like:

private void GetServers()
{
       servers = SmoApplication.EnumAvailableSqlServers(false);

       cboServer.BeginInvoke(
          (Action)(() => {
              foreach (DataRow server in servers.Rows)
              {
                  cboServer.Properties.Items.Add(server["Name"]);
              }
        }));
}

here is the right way to do it for your scenario/situation.

you should create a thread on the threadpool and let it do thread management. furthermore, when your thread gets kicked/invoked by the pool, and once you have get all your SQL Servers, you then add to the combobox by doing begin invoke so you can correctly switch between the UI thread and current thread to update your comboboxes.

Threadpool:

http://msdn.microsoft.com/en-us/library/vstudio/system.threading.threadpool.queueuserworkitem(v=vs.100).aspx

http://msdn.microsoft.com/en-us/library/vstudio/kbf0f1ct(v=vs.100).aspx

And in your method that gets invoked by the pool, add items to the combobox by doing your begininvoke.

UPDATE - I see @James posted pretty much what I am proposing. Great minds think alike.

Despite serious answers, your code needs very little to work as intended:

private void GetServers()
{
    servers = SmoApplication.EnumAvailableSqlServers(false);
    // Thread.Sleep(1);
    foreach (DataRow server in servers.Rows)
    {
          cboServer.BeginInvoke((Action)(() => { cboServer.Properties.Items.Add(server["Name"]);}));
        // Thread.Sleep(1);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM