我正在执行一些"多线程"操作,但表单在加载时暂停。
我正在尝试显示表单,然后在后台它应该在不暂停表单的情况下填充组合框。
在我的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);
}
}