C# 跨线程操作无效:控制从创建它的线程以外的线程访问
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/142003/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
提问by Prerak K
I have a scenario. (Windows Forms, C#, .NET)
我有一个场景。(Windows 窗体、C#、.NET)
- There is a main form which hosts some user control.
- The user control does some heavy data operation, such that if I directly call the
UserControl_Load
method the UI become nonresponsive for the duration for load method execution. - To overcome this I load data on different thread (trying to change existing code as little as I can)
- I used a background worker thread which will be loading the data and when done will notify the application that it has done its work.
- Now came a real problem. All the UI (main form and its child usercontrols) was created on the primary main thread. In the LOAD method of the usercontrol I'm fetching data based on the values of some control (like textbox) on userControl.
- 有一个主窗体承载一些用户控件。
- 用户控件执行一些繁重的数据操作,因此如果我直接调用该
UserControl_Load
方法,UI 在加载方法执行期间将变得无响应。 - 为了克服这个问题,我在不同的线程上加载数据(尝试尽可能少地更改现有代码)
- 我使用了一个后台工作线程,它将加载数据,完成后将通知应用程序它已经完成了它的工作。
- 现在来了一个真正的问题。所有 UI(主窗体及其子用户控件)都是在主主线程上创建的。在 usercontrol 的 LOAD 方法中,我根据 userControl 上某些控件(如文本框)的值获取数据。
The pseudocode would look like this:
伪代码如下所示:
CODE 1
代码 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.
}
}
The Exception it gave was
它给出的例外是
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.
跨线程操作无效:控制从创建它的线程以外的线程访问。
To know more about this I did some googling and a suggestion came up like using the following code
为了更多地了解这一点,我做了一些谷歌搜索,并提出了一个建议,比如使用以下代码
CODE 2
代码 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
}
}
BUT BUT BUT... it seems I'm back to square one. The Application again become nonresponsive. It seems to be due to the execution of line #1 if condition. The loading task is again done by the parent thread and not the third that I spawned.
BUT BUT BUT……看来我又回到了原点。应用程序再次变得无响应。这似乎是由于第 1 行 if 条件的执行。加载任务再次由父线程完成,而不是我生成的第三个。
I don't know whether I perceived this right or wrong. I'm new to threading.
我不知道我认为这是对还是错。我是线程的新手。
How do I resolve this and also what is the effect of execution of Line#1 if block?
我如何解决这个问题,以及执行 Line#1 if block 的效果是什么?
The situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.
情况是这样的:我想根据控件的值将数据加载到全局变量中。我不想从子线程更改控件的值。我不会从子线程中做到这一点。
So only accessing the value so that the corresponding data can be fetched from the database.
所以只有访问该值才能从数据库中获取相应的数据。
采纳答案by Jeff Hubbard
As per Prerak K's update comment(since deleted):
根据Prerak K 的更新评论(自删除后):
I guess I have not presented the question properly.
Situation is this: I want to load data into a global variable based on the value of a control. I don't want to change the value of a control from the child thread. I'm not going to do it ever from a child thread.
So only accessing the value so that corresponding data can be fetched from the database.
我想我没有正确提出问题。
情况是这样的:我想根据控件的值将数据加载到全局变量中。我不想从子线程更改控件的值。我不会从子线程中做到这一点。
所以只有访问该值才能从数据库中获取相应的数据。
The solution you want then should look like:
您想要的解决方案应该如下所示:
UserContrl1_LOadDataMethod()
{
string name = "";
if(textbox1.InvokeRequired)
{
textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
}
if(name == "MyName")
{
// do whatever
}
}
Do your serious processing in the separate thread beforeyou attempt to switch back to the control's thread. For example:
在尝试切换回控件的线程之前,请在单独的线程中进行认真的处理。例如:
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;
}
}
}
回答by Joel Coehoorn
Controls in .NET are not generally thread safe. That means you shouldn't access a control from a thread other than the one where it lives. To get around this, you need to invokethe control, which is what your 2nd sample is attempting.
.NET 中的控件通常不是线程安全的。这意味着您不应从控件所在的线程以外的线程访问控件。为了解决这个问题,您需要调用控件,这是您的第二个示例正在尝试的。
However, in your case all you've done is pass the long-running method back to the main thread. Of course, that's not really what you want to do. You need to rethink this a little so that all you're doing on the main thread is setting a quick property here and there.
但是,在您的情况下,您所做的只是将长时间运行的方法传递回主线程。当然,这并不是你真正想要做的。您需要稍微重新考虑一下,以便您在主线程上所做的一切就是在这里和那里设置一个快速属性。
回答by Jon Skeet
You only want to use Invoke
or BeginInvoke
for the bare minimum piece of work required to change the UI. Your "heavy" method should execute on another thread (e.g. via BackgroundWorker
) but then using Control.Invoke
/Control.BeginInvoke
just to update the UI. That way your UI thread will be free to handle UI events etc.
您只想使用Invoke
或BeginInvoke
用于更改 UI 所需的最少工作。您的“重”方法应该在另一个线程上执行(例如 via BackgroundWorker
),然后使用Control.Invoke
/Control.BeginInvoke
来更新 UI。这样你的 UI 线程就可以自由地处理 UI 事件等。
See my threading articlefor a WinForms example- although the article was written before BackgroundWorker
arrived on the scene, and I'm afraid I haven't updated it in that respect. BackgroundWorker
merely simplifies the callback a bit.
见我穿文章的WinForms的例子-虽然在文章之前写BackgroundWorker
赶到现场,我怕我没有更新过在这方面。BackgroundWorker
只是稍微简化了回调。
回答by Pat
You need to look at the Backgroundworker example:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspxEspecially how it interacts with the UI layer. Based on your posting, this seems to answer your issues.
您需要查看 Backgroundworker 示例:
http: //msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx特别是它如何与 UI 层交互。根据您的帖子,这似乎可以解决您的问题。
回答by Peter C
I have had this problem with the FileSystemWatcher
and found that the following code solved the problem:
我遇到了这个问题,FileSystemWatcher
发现下面的代码解决了这个问题:
fsw.SynchronizingObject = this
fsw.SynchronizingObject = this
The control then uses the current form object to deal with the events, and will therefore be on the same thread.
然后控件使用当前的表单对象来处理事件,因此将在同一线程上。
回答by Igor Brejc
The cleanest (and proper) solution for UI cross-threading issues is to use SynchronizationContext, see Synchronizing calls to the UI in a multi-threaded applicationarticle, it explains it very nicely.
UI 跨线程问题的最干净(和正确)的解决方案是使用 SynchronizationContext,请参阅多线程应用程序文章中的同步对 UI 的调用,它很好地解释了它。
回答by Ashitakalax
Here is an alternative way if the object you are working with doesn't have
如果您正在使用的对象没有,这是另一种方法
(InvokeRequired)
This is useful if you are working with the main form in a class other than the main form with an object that is in the main form, but doesn't have 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;
}
It works the same as above, but it is a different approach if you don't have an object with invokerequired, but do have access to the MainForm
它的工作原理与上面相同,但如果您没有具有 invokerequired 的对象,但可以访问 MainForm,则这是一种不同的方法
回答by RandallTo
I found a need for this while programming an iOS-Phone monotouch app controller in a visual studio winforms prototype project outside of xamarin stuidio. Preferring to program in VS over xamarin studio as much as possible, I wanted the controller to be completely decoupled from the phone framework. This way implementing this for other frameworks like Android and Windows Phone would be much easier for future uses.
在 xamarin stuidio 之外的 Visual Studio winforms 原型项目中对 iOS 手机单点触控应用程序控制器进行编程时,我发现需要这样做。我更喜欢尽可能在 VS 中编程而不是在 xamarin studio 中编程,我希望控制器与手机框架完全解耦。通过这种方式,为 Android 和 Windows Phone 等其他框架实现这一点对于未来的使用会容易得多。
I wanted a solution where the GUI could respond to events without the burden of dealing with the cross threading switching code behind every button click. Basically let the class controller handle that to keep the client code simple. You could possibly have many events on the GUI where as if you could handle it in one place in the class would be cleaner. I am not a multi theading expert, let me know if this is flawed.
我想要一个解决方案,让 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");
}
}
The GUI form is unaware the controller is running asynchronous tasks.
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);
}
}
}
}
回答by Ryszard D?egan
Threading Model in UI
UI 中的线程模型
Please read the Threading Modelin UI applications in order to understand basic concepts. The link navigates to page that describes the WPF threading model. However, Windows Forms utilizes the same idea.
请阅读UI 应用程序中的线程模型以了解基本概念。该链接导航到描述 WPF 线程模型的页面。但是,Windows 窗体利用了相同的想法。
The UI Thread
用户界面线程
- There is only one thread (UI thread), that is allowed to access System.Windows.Forms.Controland its subclasses members.
- Attempt to access member of System.Windows.Forms.Controlfrom different thread than UI thread will cause cross-thread exception.
- Since there is only one thread, all UI operations are queued as work items into that thread:
- 只有一个线程(UI 线程)允许访问System.Windows.Forms.Control及其子类成员。
- 尝试从与 UI 线程不同的线程访问System.Windows.Forms.Control 的成员将导致跨线程异常。
- 由于只有一个线程,所有 UI 操作都作为工作项排入该线程:
- If there is no work for UI thread, then there are idle gaps that can be used by a not-UI related computing.
- In order to use mentioned gaps use System.Windows.Forms.Control.Invokeor System.Windows.Forms.Control.BeginInvokemethods:
- 如果 UI 线程没有工作,那么就有空闲间隙可供非 UI 相关的计算使用。
- 为了使用提到的间隙,请使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法:
BeginInvoke and Invoke methods
BeginInvoke 和 Invoke 方法
- The computing overhead of method being invoked should be small as well as computing overhead of event handler methods because the UI thread is used there - the same that is responsible for handling user input. Regardless if this is System.Windows.Forms.Control.Invokeor System.Windows.Forms.Control.BeginInvoke.
- To perform computing expensive operation always use separate thread. Since .NET 2.0 BackgroundWorkeris dedicated to performing computing expensive operations in Windows Forms. However in new solutions you should use the async-await pattern as described here.
- Use System.Windows.Forms.Control.Invokeor System.Windows.Forms.Control.BeginInvokemethods only to update a user interface. If you use them for heavy computations, your application will block:
- 被调用方法的计算开销和事件处理程序方法的计算开销应该很小,因为 UI 线程在那里使用——与负责处理用户输入的线程相同。无论这是System.Windows.Forms.Control.Invoke还是System.Windows.Forms.Control.BeginInvoke。
- 要执行计算开销大的操作,请始终使用单独的线程。由于 .NET 2.0 BackgroundWorker致力于在 Windows 窗体中执行计算成本高的操作。但是,在新的解决方案中,您应该使用这里描述的异步等待模式。
- 仅使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法来更新用户界面。如果您将它们用于繁重的计算,您的应用程序将阻塞:
Invoke
调用
- System.Windows.Forms.Control.Invokecauses separate thread to wait till invoked method is completed:
- System.Windows.Forms.Control.Invoke导致单独的线程等待调用的方法完成:
BeginInvoke
开始调用
- System.Windows.Forms.Control.BeginInvokedoesn't cause the separate thread to wait till invoked method is completed:
- System.Windows.Forms.Control.BeginInvoke不会导致单独的线程等待调用的方法完成:
Code solution
代码解决方案
Read answers on question How to update the GUI from another thread in C#?. For C# 5.0 and .NET 4.5 the recommended solution is here.
阅读有关如何从 C# 中的另一个线程更新 GUI 的问题的答案?. 对于 C# 5.0 和 .NET 4.5,推荐的解决方案是here。
回答by UrsulRosu
For example to get the text from a Control of the UI thread:
例如从 UI 线程的 Control 中获取文本:
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