我有一个场景 . (Windows Forms,C#,.NET)
-
有一个主要表单托管一些用户控件 .
-
用户控件执行一些繁重的数据操作,这样如果我直接调用
UserControl_Load
方法,则UI在加载方法执行的持续时间内无响应 . -
为了解决这个问题,我在不同的线程上加载数据(尝试尽可能少地更改现有代码)
-
我使用了后台工作线程,它将加载数据,完成后将通知应用程序已完成其工作 .
-
现在出现了一个真正的问题 . 所有UI(主窗体及其子用户控件)都是在主要主线程上创建的 . 在usercontrol的LOAD方法中,我基于userControl上的某些控件(如文本框)的值来获取数据 .
伪代码看起来像这样:
CODE 1
UserContrl1_LoadDataMethod()
{
if (textbox1.text == "MyName") // This gives exception
{
//Load data corresponding to "MyName".
//Populate a globale variable List<string> which will be binded to grid at some later stage.
}
}
它给出的例外是
跨线程操作无效:从创建它的线程以外的线程访问控件 .
为了更多地了解这一点,我做了一些谷歌搜索,并提出了一个建议,如使用下面的代码
CODE 2
UserContrl1_LoadDataMethod()
{
if (InvokeRequired) // Line #1
{
this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
return;
}
if (textbox1.text == "MyName") // Now it wont give an exception
{
//Load data correspondin to "MyName"
//Populate a globale variable List<string> which will be binded to grid at some later stage
}
}
但是但是......似乎我又回到原点了 . 应用程序再次无响应 . 这似乎是由于条件#1执行 . 加载任务再次由父线程完成,而不是我生成的第三个 .
我不知道我是否认为这是对还是错 . 我是线程新手 .
如何解决这个问题以及阻止执行第1行的影响是什么?
The situation is this :我想根据控件的值将数据加载到全局变量中 . 我不打算从子线程中做到这一点 .
因此只访问该值,以便可以从数据库中获取相应的数据 .
20 回答
Same question : how-to-update-the-gui-from-another-thread-in-c
两种方式:
在e.result中返回值并使用它来设置backgroundWorker_RunWorkerCompleted事件中的yout文本框值
声明一些变量以将这些值保存在单独的类中(这将作为数据持有者) . 创建此类的静态实例,您可以通过任何线程访问它 .
例:
行动y; //在课堂内宣布
label1.Invoke(Y =()=> label1.Text = “文本”);
根据Prerak K's update comment(自删除后):
您想要的解决方案应该如下所示:
在尝试切换回控件的线程之前,请在单独的线程中进行严格的处理 . 例如:
UI中的线程模型
请阅读UI应用程序中的Threading Model以了解基本概念 . 该链接导航到描述WPF线程模型的页面 . 但是,Windows窗体使用相同的想法 .
UI线程
只有一个线程(UI线程),允许访问System.Windows.Forms.Control及其子类成员 .
尝试从不同于UI线程的线程访问System.Windows.Forms.Control的成员将导致跨线程异常 .
由于只有一个线程,所有UI操作都作为工作项排队到该线程中:
如果UI线程没有工作,那么非UI相关计算可以使用空闲间隙 .
为了使用上述间隙,请使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法:
BeginInvoke和Invoke方法
被调用方法的计算开销应该很小,以及事件处理程序方法的计算开销,因为在那里使用UI线程 - 负责处理用户输入的相同 . 无论是System.Windows.Forms.Control.Invoke还是System.Windows.Forms.Control.BeginInvoke .
要执行计算昂贵的操作,请始终使用单独的线程 . 由于.NET 2.0 BackgroundWorker致力于在Windows窗体中执行计算昂贵的操作 . 但是,在新的解决方案中,您应该使用here所述的async-await模式 .
仅使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法更新用户界面 . 如果您将它们用于繁重的计算,您的应用程序将阻止:
调用
BeginInvoke
代码解决方案
阅读问题How to update the GUI from another thread in C#?的答案 . 对于C#5.0和.NET 4.5,推荐的解决方案是here .
您只想使用Invoke或BeginInvoke来完成更改UI所需的最少工作量 . 您的“heavy”方法应该在另一个线程上执行(例如通过BackgroundWorker),然后使用Control.Invoke / Control.BeginInvoke来更新UI . 这样你的UI线程就可以自由处理UI事件等 .
请参阅我的threading article以获取WinForms example - 虽然文章是在BackgroundWorker到达现场之前编写的,但我在这方面更新了它 . BackgroundWorker只是略微简化了回调 .
我遇到了
FileSystemWatcher
这个问题,发现以下代码解决了这个问题:fsw.SynchronizingObject = this
然后,控件使用当前表单对象来处理事件,因此将在同一个线程上 .
.NET中的控件通常不是线程安全的 . 这意味着您不应该从它所在的线程以外的线程访问控件 . 要解决这个问题,您需要调用控件,这是您的第二个示例正在尝试的控件 .
但是,在您的情况下,您所做的就是将长时间运行的方法传递回主线程 . 当然,这不是你想要做的 . 你需要重新考虑这一点,这样你在主线程上所做的就是在这里和那里设置一个快速的属性 .
我发现需要在与表单相关的所有方法中散布的检查和调用代码过于冗长和不必要 . 这是一个简单的扩展方法,可以让您完全取消它:
然后你可以简单地这样做:
没有更多的麻烦 - 简单 .
我现在知道太晚了 . 但是,即使在今天,如果您在访问跨线程控件时遇到问题?这是迄今为止最短的答案:P
这是我从线程访问任何表单控件的方式 .
用于UI跨线程问题的最干净(和适当的)解决方案是使用SynchronizationContext,请参阅Synchronizing calls to the UI in a multi-threaded application文章,它非常好地解释了它 .
按照最简单的(在我看来)方式修改另一个线程中的对象:
使用Async / Await和回调的新外观 . 如果在项目中保留扩展方法,则只需要一行代码 .
您可以向Extension方法添加其他内容,例如将其包装在Try / Catch语句中,允许调用者告诉它在完成后返回什么类型,对调用者的异常回调:
Adding Try Catch, Auto Exception Logging and CallBack
您需要查看Backgroundworker示例:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx特别是它如何与UI层交互 . 根据您的发布,这似乎可以解答您的问题 .
在xamarin stuidio之外的visual studio winforms原型项目中编程iOS-Phone monotouch app控制器时,我发现需要这样做 . 我希望尽可能在VS中通过xamarin工作室进行编程,我希望控制器与手机框架完全分离 . 这种方式为Android和Windows Phone等其他框架实现此功能将更容易将来使用 .
我想要一个GUI可以响应事件的解决方案,而无需处理每次按钮点击后面的交叉线程切换代码的负担 . 基本上让类控制器处理它以保持客户端代码简单 . 你可能在GUI上有很多事件,好像你可以在类中的一个地方处理它会更干净 . 我不是一个多领域专家,请告诉我这是否有缺陷 .
GUI表单不知道控制器正在运行异步任务 .
这不是解决此错误的推荐方法,但您可以快速抑制它,它将完成这项工作 . 我更喜欢这个原型或演示 . 加
在
Form1()
构造函数中 .如果您正在使用的对象没有,则可以使用另一种方法
如果您使用主窗体以外的类中的主窗体并且主窗体中的对象处于主窗体但没有InvokeRequired,这将非常有用
它的工作原理与上面相同,但是如果你没有一个带有invokerequired的对象,但它有权访问MainForm,那么它是一种不同的方法
沿着与之前的答案相同的行,但是非常短的添加允许使用所有Control属性而不具有跨线程调用异常 .
Helper Method
Sample Usage
例如,从UI线程的Control获取文本:
跨线程操作有两种选择 .
第二个是使用
Control.InvokeRequired仅在从Control类继承的工作控件时有用,而SynchronizationContext可以在任何地方使用 . 一些有用的信息如下链接
Cross Thread Update UI | .Net
Cross Thread Update UI using SynchronizationContext | .Net