跨线程操作无效:从其创build的线程以外的线程访问控制

我有一个场景。 (Windows窗体,C#,.NET)

  1. 有一个主要的forms,主持一些用户控制。
  2. 用户控件执行一些繁重的数据操作,如果我直接调用UserControl_Load方法,则UI在加载方法执行期间将不响应。
  3. 为了克服这个问题,我在不同的线程上加载数据(试图尽可能less地改变现有的代码)
  4. 我使用了一个后台工作线程,它将加载数据,完成后会通知应用程序它已经完成了它的工作。
  5. 现在来了一个真正的问题。 所有的UI(主表单及其子用户控件)都是在主主线程上创build的。 在usercontrol的LOAD方法中,我基于userControl上的某些控件(如文本框)的值来获取数据。

伪代码看起来像这样:

代码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. } } 

它给的例外是

跨线程操作无效:从其创build的线程以外的线程访问控制。

要知道更多关于这个我做了一些使用Googlesearch,并提出了一个像使用下面的代码的build议

代码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行的条件。 加载任务是由父线程,而不是我产生的第三个。

我不知道我是否觉得这个对或错。 我是线程新手。

我该如何解决这个问题,以及如果block的第一行执行的效果如何呢?

情况是这样的 :我想根据控件的值将数据加载到全局variables中。 我不想从子线程更改控件的值。 我不会从一个子线程做到这一点。

所以只能访问这个值,以便相应的数据可以从数据库中获取。

根据Prera​​k K的更新评论 (自删除以来):

我想我没有正确提出这个问题。

情况是这样的:我想根据控件的值将数据加载到全局variables中。 我不想从子线程更改控件的值。 我不会从一个子线程做到这一点。

所以只能访问这个值,这样才能从数据库中获取相应的数据。

你想要的解决scheme应该是这样的:

 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 List<string> which 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中的线程模型

请阅读UI应用程序中的线程模型 ,以了解基本概念。 链接导航到描述WPF线程模型的页面。 但是,Windows窗体使用相同的想法。

UI线程

  • 只有一个线程(UI线程),允许访问System.Windows.Forms.Control及其子类成员。
  • 尝试从不同于UI线程的线程访问System.Windows.Forms.Control的成员将导致跨线程exception。
  • 由于只有一个线程,所有的UI操作都作为工作项排入该线程:

在这里输入图像描述

  • 如果UI线程没有工作,那么就会有空闲的空白,可以被非UI相关的计算使用。
  • 为了使用提到的差距使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法:

在这里输入图像描述

BeginInvoke和Invoke方法

  • 被调用的方法的计算开销应该小,以及事件处理程序方法的计算开销,因为在那里使用UI线程 – 负责处理用户input。 不pipe这是System.Windows.Forms.Control.Invoke还是System.Windows.Forms.Control.BeginInvoke 。
  • 要执行计算昂贵的操作,总是使用单独的线程 由于.NET 2.0 BackgroundWorker致力于在Windows Forms中执行计算代价高昂的操作。 但是,在新的解决scheme中,您应该使用这里描述的asynchronous等待模式。
  • 使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法只更新用户界面。 如果您将它们用于繁重的计算,您的应用程序将阻止:

在这里输入图像描述

调用

  • System.Windows.Forms.Control.Invoke导致单独的线程等待被调用的方法完成:

在这里输入图像描述

的BeginInvoke

  • System.Windows.Forms.Control.BeginInvoke不会导致单独的线程等待被调用的方法完成:

在这里输入图像描述

代码解决scheme

阅读关于问题的答案如何从C#中的另一个线程更新GUI? 。 对于C#5.0和.NET 4.5,推荐的解决scheme在这里 。

您只需要使用Invoke或BeginInvoke来完成更改UI所需的最低工作量。 你的“沉重的”方法应该在另一个线程上执行(例如通过BackgroundWorker),然后使用Control.Invoke / Control.BeginInvoke来更新UI。 这样你的UI线程将可以自由地处理UI事件等。

请参阅我的线程文章,了解WinForms示例 – 虽然文章是在BackgroundWorker到达现场之前编写的,但恐怕我没有在这方面进行更新。 BackgroundWorker只是简化了一些callback。

我有FileSystemWatcher这个问题,发现下面的代码解决了这个问题:

fsw.SynchronizingObject = this

然后控件使用当前的表单对象来处理事件,因此将在同一个线程上。

.NET中的控件通常不是线程安全的。 这意味着你不应该从一个线程以外的线程访问控件。 为了解决这个问题,你需要调用控件,这是你的第二个样本正在尝试的。

然而,在你的情况下,你所做的只是将长时间运行的方法传递回主线程。 当然,这不是你想要做的。 您需要重新思考这个问题,以便您在主线程中执行的任何操作都是快速设置属性。

我发现在与表单有关的所有方法中需要散布的检查和调用代码太冗长和不必要。 这是一个简单的扩展方法,可以让你完全消除它:

 public static class Extensions { public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) where TControlType : Control { if (control.InvokeRequired) control.Invoke(new Action(() => del(control))); else del(control); } } 

然后你可以简单地做到这一点:

 textbox1.Invoke(t => t.Text = "A"); 

没有更多的混乱 – 简单。

用于UI跨线程问题的最简洁(适当的)解决scheme是使用SynchronizationContext,请参阅在multithreading应用程序文章中将调用同步到UI中 ,它非常好地解释它。

使用Async / Await和callback的新外观。 如果将扩展方法保留在项目中,则只需要一行代码。

 /// <summary> /// A new way to use Tasks for Asynchronous calls /// </summary> public class Example { /// <summary> /// No more delegates, background workers etc. just one line of code as shown below /// Note it is dependent on the XTask class shown next. /// </summary> 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 } } /// <summary> /// 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 /// </summary> public static class XTask { /// <summary> /// RunAsync is an extension method that encapsulates the Task.Run using a callback /// </summary> /// <param name="Code">The caller is called back on the new Task (on a different thread)</param> /// <returns></returns> public async static Task RunAsync(Action Code) { await Task.Run(() => { Code(); }); return; } } 

您可以将其他内容添加到扩展方法中,例如将其包装在Try / Catch语句中,允许调用者告诉它在完成后返回什么types,调用者的exceptioncallback:

添加尝试捕捉,自动例外logging和callback

  /// <summary> /// Run Async /// </summary> /// <typeparam name="T">The type to return</typeparam> /// <param name="Code">The callback to the code</param> /// <param name="Error">The handled and logged exception if one occurs</param> /// <returns>The type expected as a competed task</returns> public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error) { var done = await Task<T>.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<bool>((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 } } 

你需要看看Backgroundworker的例子:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx特别是它如何与UI层进行交互。; 根据你的发布,这似乎回答你的问题。

在xamarin stuidio之外的Visual Studio winforms原型项目中编写iOS-Phone monotouch应用程序控制器时,我发现需要这样做。 尽可能使用VS在xamarin工作室编程,我希望控制器完全脱离电话框架。 通过这种方式为Android和Windows Phone等其他框架实现这一点,对于将来的使用来说将更容易。

我想要一个GUI可以响应事件的解决scheme,而不需要每个button点击后面的交叉线程切换代码。 基本上让类控制器处理,以保持客户端代码简单。 你可能会在GUI上有很多事件,就好像你可以在class级中的一个地方处理它一样清洁。 我不是一个多领域的专家,让我知道这是否有缺陷。

 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窗体不知道控制器正在运行asynchronous任务。

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

遵循最简单的(在我看来)的方式来修改另一个线程的对象:

 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) { Action<string> DelegateTeste_ModifyText = THREAD_MOD; Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD"); } private void THREAD_MOD(string teste) { textBox1.Text = teste; } } } 

这不是推荐的方法来解决这个错误,但你可以迅速压制它,它会做的工作。 我更喜欢这个原型或演示。 加

 CheckForIllegalCrossThreadCalls = false 

Form1()构造函数中。

如果您正在使用的对象不具有这种替代方法

 (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 

我现在知道它太晚了。 但是即使在今天,如果您在访问交叉线程控制时遇到了麻烦 这是迄今为止最短的答案:P

 Invoke(new Action(() => { label1.Text = "WooHoo!!!"; })); 

这是我如何从一个线程访问任何forms的控制。

沿着以前的答案相同的路线,但一个非常短的添加,允许使用所有控制属性没有跨线程invokationexception。

帮助者方法

 /// <summary> /// Helper method to determin if invoke required, if so will rerun method on correct thread. /// if not do nothing. /// </summary> /// <param name="c">Control that might require invoking</param> /// <param name="a">action to preform on control thread if so.</param> /// <returns>true if invoke required</returns> 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; } 

同样的问题:如何更新GUI中的另一个线程

两种方式:

  1. 返回e.result中的值并使用它在backgroundWorker_RunWorkerCompleted事件中设置文本框值

  2. 声明一些variables来将这些值保存在一个单独的类中(它将作为数据持有者)。 创build这个类的静态实例,你可以通过任何线程访问它。

例:

 public class data_holder_for_controls { //it will hold value for your label public string status = string.Empty; } class Demo { public static data_holder_for_controls d1 = new data_holder_for_controls(); static void Main(string[] args) { ThreadStart ts = new ThreadStart(perform_logic); Thread t1 = new Thread(ts); t1.Start(); t1.Join(); //your_label.Text=d1.status; --- can access it from any thread } public static void perform_logic() { //put some code here in this function for (int i = 0; i < 10; i++) { //statements here } //set result in status variable d1.status = "Task done"; } } 
 this.Invoke(new MethodInvoker(delegate { //your code here; }));