Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/335.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 线程化和Winforms表单重绘_C#_.net_Winforms_Multithreading - Fatal编程技术网

C# 线程化和Winforms表单重绘

C# 线程化和Winforms表单重绘,c#,.net,winforms,multithreading,C#,.net,Winforms,Multithreading,我刚刚编写了一个简单的应用程序来学习多线程,但我遗漏了一些东西。我启动了一个新线程,该线程执行相对较长的数据库操作(检查特定站点用户的SharePoint权限),通常长达15秒。这就是我创建线程的方式(为了简单起见,删除一些无关的代码): 我使用委托在自己的线程中激发LoadUsers,因为LoadUsers需要一个字符串。它填充一个通用列表(“代码中的列表”),我稍后使用它填充一个组合框。我的理解是,当这个线程正在处理时,我的UI不应该锁定,因为它在自己的线程上;然而,情况并非如此。直到线程完

我刚刚编写了一个简单的应用程序来学习多线程,但我遗漏了一些东西。我启动了一个新线程,该线程执行相对较长的数据库操作(检查特定站点用户的SharePoint权限),通常长达15秒。这就是我创建线程的方式(为了简单起见,删除一些无关的代码):

我使用委托在自己的线程中激发LoadUsers,因为LoadUsers需要一个字符串。它填充一个通用列表(“代码中的列表”),我稍后使用它填充一个组合框。我的理解是,当这个线程正在处理时,我的UI不应该锁定,因为它在自己的线程上;然而,情况并非如此。直到线程完成后,UI才会刷新,并且应用程序在线程处理过程中被锁定——计时器甚至从未启动过,尽管它应该每秒都在滴答作响,数据库操作需要15分钟。有人能告诉我我做错了什么吗

您的问题是

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