代码之家  ›  专栏  ›  技术社区  ›  Hardik

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

  •  2
  • Hardik  · 技术社区  · 10 年前

    我使用VS 2010,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);
    

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

    2 回复  |  直到 10 年前
        1
  •  5
  •   Hans Passant    10 年前
      void SetControlPropertyThreadSafe(...)
    

    像这样的方法有一个大问题。不幸的是,很难根除 方法 SO上有太多的帖子推荐这一点。问题是它会让程序员睡着,它是“安全”的,所以它肯定不是问题的原因。将问题转化为不可调试的问题。

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

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

    • 它隐藏了 消防水带 问题你会被诱使调用该方法来更新每一个控件,而不去考虑这会导致的问题。它正在用调用请求敲打UI线程。以比人眼所能看到的速度快50倍的速度执行此操作,您将隐藏UI线程。它永远赶不上调用请求,只要它发出一个请求,就会有另一个请求等待执行。UI线程现在不再执行其正常任务,如绘制窗口和处理用户输入。看起来 冻结的 ,就像直接运行此代码一样

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

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

        2
  •  4
  •   MBender    10 年前

    听起来你在用 BackgroundWorker 错误的

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

    正如OP所述:

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

    您不应该从 DoWork 方法。如果必须以任何方式更新UI,可以使用 ProgressChanged 上可用的事件 后台工作人员 上课和通话 ReportProgress 在执行期间 DoWork公司 。同时确保设置 WorkerReportsProgress 为真。

    这个 报告进度 方法足够灵活,允许您在必须时( ProgressChangedEventArgs 类包含 UserState 对象属性,可用于传递所需的任何类型的数据)。检查上给出的示例代码 BackgroundWorker MSND page .

    下面是我的例子:

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

    除非你 必须 向用户显示已加载的元素,同时仍在加载它们,则应使用 报告进度 显示这一进展。使用 RunWorkerCompleted 事件最终将结果传递给UI。

    如果您使用自己的UI更新代理,那么您最好放弃 后台工作人员 并使用 Task 相反