问题
Threads all share resources. That's the whole problem around multi-threaded operations.
MSDN says:
You must be careful not to manipulate any user-interface objects in your DoWork event >handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.
BackgroundWorker events are not marshaled across AppDomain boundaries. Do not use a BackgroundWorker component to perform multithreaded operations in more than one AppDomain.
And yet when I use the backgroundworker, it's not that I need to be careful not to manipulate any UI objects, it's that can't_ if I try to access the UI components from the DOWork event. The code compiles, but when the code for the DoWork runs, I get an error:
Cross-thread operation not valid: Control 'utAlerts' accessed from a thread other than the thread it was created on.
MSDN doesn't say anything about how is this done or why. Is the backgroundworker decorated with some attribute that prevents this? How is this accomplished?
回答1:
If you're handler is an instance method in your UI class, you should have access to the members of that class.
it's that my app won't even compile if I try to access the UI components from the DOWork event.
This will only happen if your DoWork
handler is static or in a different class than the UI components. In that case, you may not have access to them, as they are not visible to you.
Edit:
BackgroundWorker is intended to do "work" that is unrelated to your User Interface. You cannot change User Interface elements on any thread other than the UI thread, as user interface elements tend to have thread affinity. This actually has nothing to do with BackgroundWorker, but rather threading and user interface elements.
BW is intended to work around this by giving you progress and completion events that are automatically marshalled back onto the UI thread for you, allowing you to change UI elements there. However, you can always do this directly yourself via Control.Invoke
in Windows Forms or Dispatcher.Invoke
in WPF.
As to how this works - It depends on what framework you're using. For example, in Windows Forms, every Control
(which is the base class of all of the UI elements) has a Handle, and the Handle is internally a native window handle. This handle is used to check the window's Thread ID against the current thread ID. This allows the check to be made without storing extra variables.
回答2:
The error that you get when you try to change/update UI controls with a BackgroundWorker
has nothing to do with sharing resources over the thread. It simply states that you cannot alter a control that was created on another thread.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
textBox1.Text = "Test";
}
Results in:
Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.
This is used so that multiple threads are not accessing/changing the same controls at the same time. BackgroundWorkers
are Asynchronous and could cause a lot of problems if controls were updated while the main thread updating them as well.
I do not know how they achieved this, however, it is probably in the best interest that they prevented this from happening.
The MSDN provided another line of documentation to the segment you copied which states "BackgroundWorker events are not marshaled across AppDomain boundaries. Do not use a BackgroundWorker component to perform multithreaded operations in more than one AppDomain."
EDIT CORRESPONDES TO CONVERSATION IN COMMENTS:
private void Form1_Load(object sender, EventArgs e)
{
TextBox.CheckForIllegalCrossThreadCalls = false;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
textBox1.Text = "Test";
}
With the addition of the CheckForIllegalCrossThreadCalls = false, this code executes without error.
In the summary of the boolean property, it states that it indicates whether to "catch calls on the wrong thread."
回答3:
It can be done with something as simple as having each control store the current thread (or maybe just its ID) in a private field in the constructor and then checking if the current thread is still that one before every method. Something like this:
class ThreadAffineObject
{
private readonly Thread originalThread;
public ThreadAffineObject()
{
this.originalThread = Thread.CurrentThread;
}
private void PreventCrossThreadOperation()
{
if(Thread.CurrentThread != originalThread)
throw new CrossThreadOperationException();
}
public void DoStuff()
{
PreventCrossThreadOperation();
// Actually do stuff
}
private int someField;
public int SomeProperty
{
get { return someField; } // here reading is allowed from other threads
set
{
PreventCrossThreadOperation(); // but writing isn't
someField = value;
}
}
}
来源:https://stackoverflow.com/questions/5599164/why-cant-ui-components-be-accessed-from-a-backgroundworker