我刚刚写了一个简单的应用程序来学习多线程,我错过了一些东西。我启动一个新线程,执行相对冗长的数据库操作(检查特定站点的用户的SharePoint权限),通常长达十五秒。这就是我创建线程的方法(为简单起见,删除了一些无关的代码):
private void btnSelectSite_Click(object sender, EventArgs e)
{
strSiteURL = txtSiteURL.Text;
tmrProgressTimer.Interval = 1000;
tmrProgressTimer.Enabled = true;
ThreadStart starter = delegate { LoadUsers(strSiteURL); };
Thread t = new Thread(starter);
t.Start();
t.Join();
cboUsers.Items.Clear();
cboUsers.Items.AddRange(list.ToArray());
tmrProgressTimer.Enabled = false;
}
我正在使用委托在其自己的线程中触发LoadUsers,因为LoadUsers需要一个字符串。它填充了一个通用列表(代码中的“列表”),之后我用它来填充组合框。我的理解是,当这个线程处理时,我的UI不应该锁定,就像它在自己的线程上一样;然而,事实并非如此。在线程完成之前,UI都没有刷新,并且在线程处理期间应用程序被锁定 - 计时器甚至不会触发,尽管它应该每秒钟滴答一次,数据库操作最多需要十五次。有人能告诉我我做错了什么吗?
答案 0 :(得分:5)
正如Yuriy所说,你的用户界面被阻止,等待线程t
完成工作,这要归功于:
t.Join();
注意:由Yuriy提供的编辑解决方案也不起作用,因为组合从工作线程而不是UI更新,因为Ego发现(跨线程访问不是允许的)。击>
但是,如果你不这样做,你的代码也不会工作,因为你似乎期望list
已经被工作线程填充了。这肯定不会是这种情况。
为了将结果返回到您的组合框,当LoadUsers
方法返回时,您将(至少在C#5天之前)进行一些额外的处理。以下是对如何实施它的建议:
private void ...
{
strSiteURL = ...
System.Action after =
delegate
{
cboUsers.Items.Clear();
cboUsers.Items.AddRange(list.ToArray());
};
ThreadStart starter =
delegate
{
LoadUsers(strSiteURL);
this.Invoke(after);
};
Thread t = new Thread(starter);
t.Start();
}
当您的帖子从LoadUsers
返回时,您必须更新组合框。但是你不能在工作线程中这样做:它必须在原始UI线程上完成。为此,您必须调用Invoke
提供的Form
方法(请参阅MSDN以了解有关调用的更多信息),并将其传递给委托人。 after
委托将在UI线程上运行,一切都会很好。
我还会添加一些代码来禁用该按钮,这样用户就不会启动多个线程。当线程调用after
时,您可以通过重新启用按钮来完成。
并注意例外情况: - )
答案 1 :(得分:4)
您的问题是
t.Join();
您正在阻止调用线程,直到t
完成。
您可以使用:
private void btnSelectSite_Click(object sender, EventArgs e)
{
strSiteURL = txtSiteURL.Text;
tmrProgressTimer.Interval = 1000;
tmrProgressTimer.Enabled = true;
ThreadStart starter = () =>
{
LoadUsers(strSiteURL);
cboUsers.Invoke(() =>
{
cboUsers.Items.Clear();
cboUsers.Items.AddRange(list.ToArray());
tmrProgressTimer.Enabled = false;
});
};
Thread t = new Thread(starter);
t.Start();
}
答案 2 :(得分:2)
我只是在重述别人已经说过的话。通过在辅助线程上调用Join()
,您可以通过强制UI线程等到辅助线程完成来有效地中和辅助线程的好处。正如您所指出的,您希望两个线程同时运行,辅助线程在结束时通知UI线程结果。
使用正式的Thread
对象来做这件事并没有错,但它是重量级的。对于UI的后台任务,我强烈建议使用System.ComponentModel.BackgroundWorker
类。此类提供了一个定义良好的接口,允许您在后台线程运行时定期更新UI。
有关.NET中各种线程备选方案的一般经验法则,您可以参考the answer here。