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

时间:2014-09-11 09:21:02

标签: c# multithreading winforms

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

我不明白我做错了。 为什么该计划挂起?

2 个答案:

答案 0 :(得分:5)

  void SetControlPropertyThreadSafe(...)

这样的方法存在很大问题。不幸的是很难根除,有太多方式过多的帖子推荐这个。问题是它让一个程序员沉睡,它是“安全的”所以肯定不是问题的原因。把问题变成一个不容易被解决的问题。

关于它绝对没有“安全”:

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

  • 它隐藏了 fire-hose 问题。你会痴迷于调用方法来更新每一个控件,而不是考虑这个导致的问题。这是用调用请求打击UI线程。这样做的速度比人眼可以看到的高50倍,你会埋葬UI线程。它永远无法赶上调用请求,只要它调度一个然后另一个等待执行。 UI线程现在停止处理其正常职责,例如绘制窗口和处理用户输入。它看起来像冻结,就像直接运行此代码一样

  • 当用户关闭窗口但您的工作线程保持驾驶时会发生非常不愉快的事情。调用不再存在的窗口。这个通常发出响亮的声音,因此诊断起来并不太难。有时它不能,不可能调试,在停止线程和允许窗口关闭之间存在不可避免的线程竞争,只能通过不关闭窗口而是隐藏它来解决。

你的问题是第二颗子弹。它挂起是因为你的代码现在运行得更快。 Invoke()调用隐藏了调试器中的问题。完全摆脱此代码,危险,并从列表<>中收集dbase查询的结果。偶尔将它传递给ReportProgress()方法,这样就不会阻塞UI线程。在调用后重新创建List,因此它是线程安全的。

答案 1 :(得分:4)

听起来你正在使用BackgroundWorker ......错误。

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

OP表示:

  

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

您不应该手动更新DoWork方法的用户界面。如果您必须以任何方式更新用户界面,则可以使用ProgressChanged课程中提供的BackgroundWorker事件,并在执行ReportProgress期间调用DoWork。另外,请务必将WorkerReportsProgress设置为true。

ReportProgress方法非常灵活,如果必须,您可以将几乎任何内容传递回UI(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;
    }

除非必须在仍然加载时向用户显示已加载的元素,否则您应该使用ReportProgress来显示 - 进度。使用RunWorkerCompleted事件最终将结果传递给用户界面。

如果您使用自己的UI更新代理,那么您也可以完全放弃BackgroundWorker并改为使用Task