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

本文关键字:线程 工作 后台 挂起 应用程序 Winforms | 更新日期: 2023-09-27 18:21:32

我使用VS 2010 C#构建了应用程序。

我在应用程序中使用BackgroundWorker

当我点击按钮时,代码从数据库中获取记录并显示到Datagrid中。但问题是,当我从代码中运行它时,它运行得很好,但当我运行程序.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);

我不明白我哪里做错了。为什么程序挂起?

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

  void SetControlPropertyThreadSafe(...)

像这样的方法有一个大问题。不幸的是,很难根除,SO上有太多方式的帖子推荐这样做。问题是它会让程序员入睡,它是"安全的",所以它肯定不是问题的原因。把一个问题变成一个不可破解的问题。

它绝对没有什么"安全"可言:

  • 使用Control.Invoke()是危险的,它很容易导致死锁。当UI线程中有代码做了一些不明智的事情,比如等待工作线程完成时触发。只有当你背靠墙时才使用Invoke(),实际上需要返回值。当你发现这是的必要性时,不要这样做,除非你禁用UI,否则它总是会竞争。始终使用BeginInvoke()。

  • 它隐藏了消防软管问题。您会被诱骗调用该方法来更新每一个控件,而不去考虑这会导致的问题。它正在用调用请求敲打UI线程。以比人眼所能看到的速度高出约50倍的速度执行此操作,您将掩埋UI线程。它永远无法赶上调用请求,一旦它调度了一个调用请求,就会有另一个请求等待执行。UI线程现在不再承担其正常职责,比如绘制窗口和处理用户输入。它看起来冻结,就像你直接运行时一样

  • 当用户关闭窗口,但你的工作线程仍在运行时,会发生非常不愉快的事情。调用一个已不存在的窗口。这个通常会发出很大的爆炸声,所以诊断起来并不太难。有时情况并非如此,无法调试,在停止线程和允许窗口关闭之间不可避免地会出现线程竞争,只有通过不关闭窗口而是隐藏窗口才能解决这一问题。

你的问题是第二颗子弹。它挂起是因为您的代码现在运行得更快了。Invoke()调用将问题隐藏在调试器中。完全去掉这段代码,它是危险的,并在List<>中收集dbase查询的结果。每隔一段时间将它传递给ReportProgress()方法,这样就不会对UI线程进行处理。在调用后重新创建List,使其线程安全。

听起来像是在使用BackgroundWorker。。。错误的

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

如OP所述:

需要委托才能调用线程安全。SetControlPropertyThreadSafe(dataGridView1, "DataSource", dtGrid2);正在Backgroundworker_DoWork()下运行。。当我的应用程序运行时,我需要在主窗口上显示输出。

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

ReportProgress方法足够灵活,如果必须的话,可以将几乎所有内容都传递回UI(ProgressChangedEventArgs类包含一个UserState对象属性,您可以使用它来传递任何类型的数据)。检查BackgroundWorker MSND页面上给出的示例代码。

这是我的例子:

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

除非必须在加载已经加载的元素时向用户显示这些元素,否则应该使用ReportProgress来显示进度。使用RunWorkerCompleted事件最终将结果传递给UI。

如果您使用自己的UI更新委托,那么不妨完全放弃BackgroundWorker,转而使用Task