多线程暂停窗体



我正在执行一些"多线程"操作,但表单在加载时暂停。

我正在尝试显示表单,然后在后台它应该在不暂停表单的情况下填充组合框。

在我的Form_Load事件中,我有以下内容:

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

在我的GetServers()方法中:

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

我在这里错过了什么?表单不应该暂停,它应该工作,然后最终当线程完成时,它应该只填充组合框。

是的,所以它阻止UI的原因只是因为新线程中没有运行真正的代码。在对GetServers的调用中,您回调到UI线程,然后做繁忙的事情(您也可以在这里完全不使用线程…)

你想把任何长时间运行的工作放在线程中,只有当你想更新它时才回调到UI线程中,例如

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());
}

注意事项

  • 尝试多线程时不要使用DoEvents,尤其是当您不了解它的用法时
  • 除非确定需要,否则不要启动新线程(例如,长时间运行的I/O任务)
  • 不要使用Thread.Sleep来模拟"暂停"(您似乎正在这样做)
  • 请将线程池用于短时间的工作(如下图所示)

由于BeginInvoke,所有代码都在UI线程上执行。

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?
                }
            })
        );
}

首先:创建一个线程将花费少量时间。此外,您正在使用BeginInvoke 将实际工作调度回UI

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

您应该尝试将除UI更新之外的所有内容从BeginInvoke委托中移出,类似于:

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

以下是针对您的场景/情况进行此操作的正确方法。

您应该在线程池上创建一个线程,并让它进行线程管理。此外,当你的线程被池踢/调用时,一旦你得到了所有的SQL Server,你就可以通过begin invoke添加到组合框中,这样你就可以正确地在UI线程和当前线程之间切换以更新你的组合框。

线程池:

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

在池调用的方法中,通过执行begininvoke将项添加到组合框中。

更新-我看到@James几乎发布了我的提议。伟大的人思想相同。

尽管答案很严肃,但您的代码几乎不需要什么就能按预期工作:

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

最新更新