C# Winforms线程应用程序挂起(后台工作线程)

C# Winforms线程应用程序挂起(后台工作线程),c#,multithreading,winforms,C#,Multithreading,Winforms,我已经使用VS2010,C#构建了应用程序 我正在应用程序中使用BackgroundWorker 当我点击按钮时,代码从数据库中获取记录并显示到Datagrid中。 但问题是,当我从代码运行它时,它工作正常,但当我运行program.exe时,它被挂起 //Declared delegate delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propert

我已经使用VS2010,C#构建了应用程序

我正在应用程序中使用
BackgroundWorker

当我点击按钮时,代码从数据库中获取记录并显示到Datagrid中。 但问题是,当我从代码运行它时,它工作正常,但当我运行program.exe时,它被挂起

//Declared delegate
delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propertyValue);

//Declared method to run control Thread safe
public static void SetControlPropertyThreadSafe(Control control, string propertyName, object propertyValue)
{
    if (control.InvokeRequired)
    {   
        control.Invoke(new SetControlPropertyThreadSafeDelegate(SetControlPropertyThreadSafe), new object[] { control, propertyName, propertyValue });
    }
    else
    {              
        control.GetType().InvokeMember(propertyName, BindingFlags.SetProperty, null, control, new object[] { propertyValue });
    }
}


//calling method like below
SetControlPropertyThreadSafe(dataGridView1, "DataSource", dtGrid2);
我不明白我哪里做错了。 为什么程序挂起

  void SetControlPropertyThreadSafe(...)
像这样的方法有一个大问题。不幸的是很难根除,有太多的职位,所以建议这一点。问题是它会让程序员昏昏欲睡,它是“安全的”,所以肯定不是问题的原因。把一个问题变成一个不可质疑的问题

绝对没有什么“安全”可言:

  • 使用Control.Invoke()是危险的,它很容易导致死锁。当UI线程中的代码执行不明智的操作(如等待工作线程完成)时触发。只有当您背对着墙时才使用Invoke(),实际上需要返回值。当你发现这是必要的,然后不要这样做,它总是比赛,除非你禁用用户界面。请始终使用BeginInvoke()

  • 它隐藏了消防水管的问题。您将被诱骗调用该方法来更新每个控件,而不考虑由此导致的问题。这会用调用请求来打击UI线程。这样做的速度要比人眼所能看到的速度高50倍左右,你就会把UI线程埋了。它永远也赶不上调用请求,一旦它发送了一个调用请求,就会有另一个调用请求等待执行。UI线程现在不再负责它的正常任务,比如绘制窗口和处理用户输入。它看起来是冻结的,就像直接运行此代码一样

  • 当用户关闭窗口但工作线程仍在运行时,会发生非常不愉快的事情。调用不再存在的窗口。这通常会发出一声巨响,因此不太难诊断。有时不可能,不可能调试,在停止线程和允许窗口关闭之间不可避免地存在线程竞争,只有通过不关闭窗口而是隐藏窗口才能解决这一问题


你的问题是第二颗子弹。它挂起是因为您的代码现在运行得更快。Invoke()调用将问题隐藏在调试器中。完全摆脱这段代码,它是危险的,并从数据库查询中收集结果,比如说,在一个列表中。每隔一段时间将其传递给ReportProgress()方法,这样您就不会使用UI线程。在调用后重新创建列表,使其成为线程安全的。

听起来您正在使用
BackgroundWorker
。。。错

BackgroundWorker
类允许您在后台执行操作,向UI提供有关进度的信息,并最终返回完整的结果

正如OP所说:

需要委托才能调用线程安全<代码>SetControlPropertyThreadSafe(dataGridView1,“数据源”,dtGrid2)正在Backgroundworker_DoWork()下运行。。当我的应用程序运行时,我需要在主窗口上显示输出

您不应该手动从
DoWork
方法更新UI。如果必须以任何方式更新UI,可以使用
BackgroundWorker
类上可用的
ProgressChanged
事件,并在执行
DoWork
期间调用
ReportProgress
。还要确保将
WorkerReportsProgress
设置为true

ReportProgress
方法非常灵活,允许您在必要时将几乎任何内容传递回UI(
ProgressChangedEventArgs
类包含一个
UserState
对象属性,您可以使用该属性传递任何类型的数据)。检查上给出的示例代码

下面是我的例子:

    System.ComponentModel.BackgroundWorker worker = new System.ComponentModel.BackgroundWorker();

    void StartBackgroundTask()
    {
        worker.DoWork += worker_DoWork;
        //if it's possible to display progress, use this
        worker.WorkerReportsProgress = true;
        worker.ProgressChanged += worker_ProgressChanged;
        //what to do when the method finishes?
        worker.RunWorkerCompleted += worker_RunWorkerCompleted;
        //start!
        worker.RunWorkerAsync();
    }

    void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
        //perform any "finalization" operations, like re-enable disabled buttons
        //display the result using the data in e.Result
        //this code will be running in the UI thread
    }

    //example of a container class to pass more data in the ReportProgress event
    public class ProgressData
    {
        public string OperationDescription { get; set; }
        public int CurrentResult { get; set; }
        //feel free to add more stuff here
    }

    void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
        //display the progress using e.ProgressPercentage or e.UserState
        //this code will be running in the UI thread
        //UserState can be ANYTHING:
        //var data = (ProgressData)e.UserState;
    }

    void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
    {
        //this code will NOT be running in the UI thread!
        //you should NOT call the UI thread from this method

        int result = 1;
        //perform calculations
        for (var i = 1; i <= 10; i++)
        {
            worker.ReportProgress(i, new ProgressData(){ OperationDescription = "CustomState passed as second, optional parameter", CurrentResult = result });
            System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
            result *= i;
        }

        e.Result = result;
    }
System.ComponentModel.BackgroundWorker worker=new System.ComponentModel.BackgroundWorker();
void StartBackgroundTask()
{
worker.DoWork+=worker\u DoWork;
//如果可以显示进度,请使用此选项
worker.WorkerReportsProgress=true;
worker.ProgressChanged+=worker\u ProgressChanged;
//方法完成后要做什么?
worker.RunWorkerCompleted+=worker\u RunWorkerCompleted;
//开始!
worker.RunWorkerAsync();
}
void worker\u RunWorkerCompleted(对象发送方,System.ComponentModel.RunWorkerCompletedEventArgs e)
{
//执行任何“终结”操作,如重新启用禁用的按钮
//使用e.result中的数据显示结果
//此代码将在UI线程中运行
}
//在ReportProgress事件中传递更多数据的容器类示例
公共类进程数据
{
公共字符串操作说明{get;set;}
public int CurrentResult{get;set;}
//请随意在这里添加更多内容
}
void worker\u ProgressChanged(对象发送方,System.ComponentModel.ProgressChangedEventArgs e)
{
//使用e.ProgressPercentage或e.UserState显示进度
//此代码将在UI线程中运行
//UserState可以是任何内容:
//var data=(ProgressData)e.UserState;
}
无效工作线程(对象发送器,System.ComponentModel.DoWorkEventArgs e)
{
//此代码将不会在UI线程中运行!
//您不应该从此方法调用UI线程
int结果=1;
//计算

对于(var i=1;我将调试器附加到挂起的应用程序并“中断”它。这里应该会显示它是“挂起的”。但当我在调试模式下运行它时,hungI并没有说“将调试器附加到挂起的应用程序”,而不是在调试模式下运行它。VS可以连接到大多数应用程序或线程以进行调试。哦,好的。。