我有一个场景.(Windows Forms,C#,.NET)
有一个主要表单托管一些用户控件.
用户控件执行一些繁重的数据操作,这样如果我直接调用了 UserControl_Load
方法,则UI在加载方法执行的持续时间内变得无响应.
为了克服这个问题,我在不同的线程上加载数据(尝试尽可能少地更改现有代码)
我使用了后台工作线程来加载数据,完成后会通知应用程序已经完成了它的工作.
现在来了一个真正的问题.所有UI(主窗体及其子用户控件)都是在主要主线程上创建的.在usercontrol的LOAD方法中,我基于userControl上的某些控件(如文本框)的值来获取数据.
伪代码看起来像这样:
代码1
UserContrl1_LoadDataMethod() { if (textbox1.text == "MyName") // This gives exception { //Load data corresponding to "MyName". //Populate a globale variable Listwhich will be binded to grid at some later stage. } }
它给出的例外是
跨线程操作无效:从创建它的线程以外的线程访问控件.
为了更多地了解这一点,我做了一些谷歌搜索,并提出了一个建议,如使用下面的代码
代码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 Listwhich will be binded to grid at some later stage } }
但是但是......似乎我又回到原点了.应用程序再次无响应.这似乎是由于条件#1执行.加载任务再次由父线程完成,而不是我生成的第三个.
我不知道我是否认为这是对还是错.我是线程新手.
如何解决这个问题以及阻止执行第1行的影响是什么?
情况是这样的:我想根据控件的值将数据加载到全局变量中.我不想从子线程更改控件的值.我不打算从子线程中做到这一点.
因此只访问该值,以便可以从数据库中获取相应的数据.
根据Prerak K的更新评论(自删除后):
我想我没有正确提出这个问题.
情况是这样的:我想根据控件的值将数据加载到全局变量中.我不想从子线程更改控件的值.我不打算从子线程中做到这一点.
因此只访问该值,以便可以从数据库中获取相应的数据.
您想要的解决方案应该如下所示:
UserContrl1_LOadDataMethod() { string name = ""; if(textbox1.InvokeRequired) { textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; })); } if(name == "MyName") { // do whatever } }
在尝试切换回控件的线程之前,请在单独的线程中进行严格的处理.例如:
UserContrl1_LOadDataMethod() { if(textbox1.text=="MyName") //<<======Now it wont give exception** { //Load data correspondin to "MyName" //Populate a globale variable Listwhich will be //bound to grid at some later stage if(InvokeRequired) { // after we've done all the processing, this.Invoke(new MethodInvoker(delegate { // load the control with the appropriate data })); return; } } }
请阅读UI应用程序中的线程模型以了解基本概念.该链接导航到描述WPF线程模型的页面.但是,Windows窗体使用相同的想法.
只有一个线程(UI线程),允许访问System.Windows.Forms.Control及其子类成员.
尝试从不同于UI线程的线程访问System.Windows.Forms.Control的成员将导致跨线程异常.
由于只有一个线程,所有UI操作都作为工作项排队到该线程中:
如果UI线程没有工作,那么可以由非UI相关计算使用空闲间隙.
为了使用提到的间隙,请使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法:
被调用的方法的计算开销应该很小以及事件处理程序方法的计算开销,因为在那里使用UI线程 - 负责处理用户输入的相同.无论这是System.Windows.Forms.Control.Invoke还是System.Windows.Forms.Control.BeginInvoke.
要执行计算昂贵的操作总是使用单独的线程.自.NET 2.0开始,BackgroundWorker致力于在Windows窗体中执行昂贵的计算操作.但是,在新的解决方案中,您应该使用此处所述的async-await模式.
仅使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法更新用户界面.如果您将它们用于繁重的计算,您的应用程序将阻止:
System.Windows.Forms.Control.Invoke导致单独的线程等待,直到调用的方法完成:
System.Windows.Forms.Control.BeginInvoke不会导致单独的线程等待,直到调用的方法完成:
阅读有关问题的答案如何从C#中的另一个线程更新GUI?.对于C#5.0和.NET 4.5,推荐的解决方案就在这里.
您只想使用Invoke或BeginInvoke来完成更改UI所需的最少工作量.你的"重"方法应该在另一个线程上执行(例如通过BackgroundWorker),然后使用Control.Invoke/Control.BeginInvoke来更新UI.这样你的UI线程就可以自由处理UI事件等.
请参阅我的线程文章以获取WinForms示例 - 虽然文章是在BackgroundWorker到达现场之前编写的,但我担心我没有在这方面更新它.BackgroundWorker只是略微简化了回调.
我遇到了这个问题,FileSystemWatcher
发现以下代码解决了这个问题:
fsw.SynchronizingObject = this
然后,控件使用当前表单对象来处理事件,因此将在同一个线程上.
我现在知道太晚了.但是,即使在今天,如果您在访问跨线程控件时遇到问题?这是迄今为止最短的答案:P
Invoke(new Action(() => { label1.Text = "WooHoo!!!"; }));
这是我从线程访问任何表单控件的方式.
.NET中的控件通常不是线程安全的.这意味着您不应该从它所在的线程以外的线程访问控件.要解决这个问题,您需要调用控件,这是您的第二个示例正在尝试的控件.
但是,在您的情况下,您所做的就是将长时间运行的方法传递回主线程.当然,这不是你想要做的.你需要重新考虑这一点,这样你在主线程上所做的就是在这里和那里设置一个快速的属性.
我发现需要在与表单相关的所有方法中散布的检查和调用代码过于冗长和不必要.这是一个简单的扩展方法,可以让您完全取消它:
public static class Extensions { public static void Invoke(this TControlType control, Action del) where TControlType : Control { if (control.InvokeRequired) control.Invoke(new Action(() => del(control))); else del(control); } }
然后你可以简单地这样做:
textbox1.Invoke(t => t.Text = "A");
没有更多的麻烦 - 简单.
用于UI跨线程问题的最简洁(和适当的)解决方案是使用SynchronizationContext,请参阅在多线程应用程序文章中同步对UI的调用,它非常好地解释了它.
使用Async/Await和回调的新外观.如果在项目中保留扩展方法,则只需要一行代码.
////// A new way to use Tasks for Asynchronous calls /// public class Example { ////// No more delegates, background workers etc. just one line of code as shown below /// Note it is dependent on the XTask class shown next. /// public async void ExampleMethod() { //Still on GUI/Original Thread here //Do your updates before the next line of code await XTask.RunAsync(() => { //Running an asynchronous task here //Cannot update GUI Thread here, but can do lots of work }); //Can update GUI/Original thread on this line } } ////// A class containing extension methods for the Task class /// Put this file in folder named Extensions /// Use prefix of X for the class it Extends /// public static class XTask { ////// RunAsync is an extension method that encapsulates the Task.Run using a callback /// /// The caller is called back on the new Task (on a different thread) ///public async static Task RunAsync(Action Code) { await Task.Run(() => { Code(); }); return; } }
您可以向Extension方法添加其他内容,例如将其包装在Try/Catch语句中,允许调用者告诉它在完成后返回什么类型,对调用者的异常回调:
添加Try Catch,Auto Exception Logging和CallBack
////// Run Async /// ///The type to return /// The callback to the code /// The handled and logged exception if one occurs ///The type expected as a competed task public async static TaskRunAsync (Func Code, Action Error) { var done = await Task .Run(() => { T result = default(T); try { result = Code("Code Here"); } catch (Exception ex) { Console.WriteLine("Unhandled Exception: " + ex.Message); Console.WriteLine(ex.StackTrace); Error(ex); } return result; }); return done; } public async void HowToUse() { //We now inject the type we want the async routine to return! var result = await RunAsync ((code) => { //write code here, all exceptions are logged via the wrapped try catch. //return what is needed return someBoolValue; }, error => { //exceptions are already handled but are sent back here for further processing }); if (result) { //we can now process the result because the code above awaited for the completion before //moving to this statement } }
这不是解决此错误的推荐方法,但您可以快速抑制它,它将完成这项工作.我更喜欢这个原型或演示.加
CheckForIllegalCrossThreadCalls = false
在Form1()
构造函数中.
您需要查看Backgroundworker示例:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
特别是它如何与UI层交互.根据您的发布,这似乎可以解答您的问题.
按照最简单的(在我看来)方式修改另一个线程中的对象:
using System.Threading.Tasks; using System.Threading; namespace TESTE { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { ActionDelegateTeste_ModifyText = THREAD_MOD; Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD"); } private void THREAD_MOD(string teste) { textBox1.Text = teste; } } }
在xamarin stuidio之外的visual studio winforms原型项目中编程iOS-Phone monotouch app控制器时,我发现需要这样做.我希望尽可能在VS中通过xamarin工作室进行编程,我希望控制器与手机框架完全分离.这种方式为Android和Windows Phone等其他框架实现此功能将更容易将来使用.
我想要一个GUI可以响应事件的解决方案,而无需处理每次按钮点击后面的交叉线程切换代码的负担.基本上让类控制器处理它以保持客户端代码简单.你可能在GUI上有很多事件,好像你可以在类中的一个地方处理它会更干净.我不是一个多领域专家,请告诉我这是否有缺陷.
public partial class Form1 : Form { private ExampleController.MyController controller; public Form1() { InitializeComponent(); controller = new ExampleController.MyController((ISynchronizeInvoke) this); controller.Finished += controller_Finished; } void controller_Finished(string returnValue) { label1.Text = returnValue; } private void button1_Click(object sender, EventArgs e) { controller.SubmitTask("Do It"); } }
GUI表单不知道控制器正在运行异步任务.
public delegate void FinishedTasksHandler(string returnValue); public class MyController { private ISynchronizeInvoke _syn; public MyController(ISynchronizeInvoke syn) { _syn = syn; } public event FinishedTasksHandler Finished; public void SubmitTask(string someValue) { System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue)); } private void submitTask(string someValue) { someValue = someValue + " " + DateTime.Now.ToString(); System.Threading.Thread.Sleep(5000); //Finished(someValue); This causes cross threading error if called like this. if (Finished != null) { if (_syn.InvokeRequired) { _syn.Invoke(Finished, new object[] { someValue }); } else { Finished(someValue); } } } }
如果您正在使用的对象没有,这是另一种方法
(InvokeRequired)
如果您使用的是主窗体中的对象,但没有InvokeRequired,则该主窗体在除主窗体以外的类中使用主窗体时很有用
delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text); private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text) { MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text); } public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text) { objectWithoutInvoke.Text = text; }
它的工作原理与上面相同,但是如果您没有带invokerequired的对象,但是可以访问MainForm,则它是不同的方法
例如,要从UI线程的控件获取文本:
Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String
Private Function GetControlText(ByVal ctl As Control) As String
Dim text As String
If ctl.InvokeRequired Then
text = CStr(ctl.Invoke(
New GetControlTextInvoker(AddressOf GetControlText), ctl))
Else
text = ctl.Text
End If
Return text
End Function
与以前的答案相同,只是增加了很短的内容,允许使用所有Control属性而没有跨线程调用异常。
辅助方法
////// Helper method to determin if invoke required, if so will rerun method on correct thread. /// if not do nothing. /// /// Control that might require invoking /// action to preform on control thread if so. ///true if invoke required public bool ControlInvokeRequired(Control c, Action a) { if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate { a(); })); else return false; return true; }
样品用量
// usage on textbox public void UpdateTextBox1(String text) { //Check if invoke requied if so return - as i will be recalled in correct thread if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return; textBox1.Text = ellapsed; } //Or any control public void UpdateControl(Color c, String s) { //Check if invoke requied if so return - as i will be recalled in correct thread if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return; myControl.Text = s; myControl.BackColor = c; }
this.Invoke(new MethodInvoker(delegate { //your code here; }));