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);
我不明白我哪里做错了。为什么程序挂起?
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
。