线程和Winforms形式重绘

时间:2011-03-01 17:49:28

标签: c# .net winforms multithreading

我刚刚写了一个简单的应用程序来学习多线程,我错过了一些东西。我启动一个新线程,执行相对冗长的数据库操作(检查特定站点的用户的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都没有刷新,并且在线程处理期间应用程序被锁定 - 计时器甚至不会触发,尽管它应该每秒钟滴答一次,数据库操作最多需要十五次。有人能告诉我我做错了什么吗?

3 个答案:

答案 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