C# 线程化和Winforms表单重绘
我刚刚编写了一个简单的应用程序来学习多线程,但我遗漏了一些东西。我启动了一个新线程,该线程执行相对较长的数据库操作(检查特定站点用户的SharePoint权限),通常长达15秒。这就是我创建线程的方式(为了简单起见,删除一些无关的代码): 我使用委托在自己的线程中激发LoadUsers,因为LoadUsers需要一个字符串。它填充一个通用列表(“代码中的列表”),我稍后使用它填充一个组合框。我的理解是,当这个线程正在处理时,我的UI不应该锁定,因为它在自己的线程上;然而,情况并非如此。直到线程完成后,UI才会刷新,并且应用程序在线程处理过程中被锁定——计时器甚至从未启动过,尽管它应该每秒都在滴答作响,数据库操作需要15分钟。有人能告诉我我做错了什么吗 您的问题是C# 线程化和Winforms表单重绘,c#,.net,winforms,multithreading,C#,.net,Winforms,Multithreading,我刚刚编写了一个简单的应用程序来学习多线程,但我遗漏了一些东西。我启动了一个新线程,该线程执行相对较长的数据库操作(检查特定站点用户的SharePoint权限),通常长达15秒。这就是我创建线程的方式(为了简单起见,删除一些无关的代码): 我使用委托在自己的线程中激发LoadUsers,因为LoadUsers需要一个字符串。它填充一个通用列表(“代码中的列表”),我稍后使用它填充一个组合框。我的理解是,当这个线程正在处理时,我的UI不应该锁定,因为它在自己的线程上;然而,情况并非如此。直到线程完
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();
}
正如Yuriy所回答的,您的UI被阻止,等待线程
t
完成其工作,这要感谢调用:
t.Join();
注意:Yuriy提供的已编辑解决方案也不起作用,因为正如Ego发现的那样,combo是从工作线程更新的,而不是从UI更新的(不允许跨线程访问)
但是,如果不这样做,代码也不会工作,因为您似乎希望列表已经由工作线程填充。这肯定不是事实
为了将结果返回到组合框,当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
方法(请参阅以了解有关调用的更多信息),并向其传递一个委托。after
委托将在UI线程上运行,一切都会很好
我还添加了一些代码来禁用按钮,这样用户就不会启动多个线程。当线程在
之后调用时,您可以通过重新启用按钮来完成
注意例外情况:-)我只是重复别人已经说过的话。通过在次线程上调用Join()
,可以强制UI线程等待次线程完成,从而有效地抵消了次线程的好处。正如您所注意到的,您希望两个线程同时运行,第二个线程在完成时将结果通知UI线程
使用一个正式的线程
对象来实现这一点没有错,但它是重量级的。对于UI中的后台任务,我强烈建议使用该类。此类提供了一个定义良好的接口,允许您在后台线程运行时定期更新UI
有关.NET中各种线程替代方案的一般经验法则,请参考。我在尝试清除组合框(cboUsers.Items.clear();)时遇到以下错误:“跨线程操作无效:从创建控件的线程以外的线程访问控件“cboUsers”。@Geo对此表示抱歉,我会在一秒钟内修复它。@Yuriy:我的答案包含缺少Invoke()
@Yuriy:两者大致相当;这只是品味的问题。我尽量简洁一点,这样对于不熟悉兰博达斯的人来说事情就更清楚了。@Pierre-1。Lambda表达式似乎是C#的方向。似乎委托语法主要是为了向后兼容。谢谢!我不得不尝试一下(同样,我只是从多线程开始),但它很有魅力。你对何时使用哪种类型的线程的分析很好。谢谢
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();
}