Is BackgroundWorker in c# Thread Safe?
The reason I ask this is because I get a开发者_如何学Go
Controls created on one thread cannot be parented to a control on a different thread
exception with it. This is my DoWork
event code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var openFile = document.Open(MyFileName);
e.Result = openFile;
}
where document
is an UI control that is initialized when the parent form is created. During Open
method various properties in document
will be filled.
I tried to change the code to invoke, yet the same problem persists. i.e,
document.GetType().GetMethod("Open)".Invoke(document, new object[]{MyFileName})
will yield the same error as the above.
Any idea how to manipulate the document
control? In other words, how to make the above code work?
Edit: It was suggested that I use Control.Invoke
, but it still didn't work ( both of the threads hanged). This is the code I tried:
private delegate bool OpenFile(string filePath);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
OpenFile oF = new OpenFile(document.Open);
var openFile = Invoke(oF, MyFileName); // it doesn't really matter whether I use BeginInvoke or Invoke, or other Control.Invoke, the end result is the same. Both the main thread hosting the document and the thread that launches the UI hanged.
e.Result = openFile;
}
It isn't the thread that's the problem it's the fact that it's trying to call a method on a UI control. In both WPF and WinForms controls can only be called on the UI thread (of which there is typically one). You don't say which you are using but you need to call the Control.Invoke
method for WinForms or Dispatcher.Invoke
for WPF.
The Invoke()
reflection method you show will actually invoke the method on the current thread.
You can either invoke as Mehrdad Afshari suggested, or you can make use of the bgw's progress event which comes back on the UI thread. Or the work completed event which also comes back on the UI thread. The difference between the two is WorkCompleted is fired only once at the end. Progress is fired by you from DoWork.
While it's unclear to me what you exactly mean by thread-safety of a BackgroundWorker
, the problem is not that object; Windows Forms controls are designed to be manipulated on a single thread (the UI thread). You should not manipulate Windows Forms objects on different threads. You can invoke actions in the UI thread from other threads by using the Control.Invoke
method (the Invoke
method you are currently using is provided by reflection and is totally unrelated to this problem):
Invoke(new Action(MethodToRunInUIThread));
void MethodToRunInUIThread() {
// do stuff here.
}
By the way, it doesn't make sense to use a background worker if all you are doing is manipulating UI objects.
If that functionality of the UI Control takes that long to execute, there may not be much you can do. "Freezing" occurs when a long-running operation happens on the UI thread, and if that function of the control was not specifically made thread-safe, it must be run on the main thread.
Normally, you'd want to separate the "document" functionality away from the control that displays it. This way, your document could be loaded on a separate, independent thread and be displayed later when ready. Otherwise, the control itself would have to implement a multi-threaded load routine to slow loading freezes.
Since you've specified this is a third party control in your comments, you may be out of luck here.
BackgroundWorker is a thread based structure. The thread-safety matter is about functions when doing simultaneous tasks. Maybe what you ask for is about winforms controls which are accessed through a unique thread, that of the user interface thread.
You need to use Control.BeginInvoke() in DoWork. This executes the delegate asynchronously and so will ensure the calling thread will not "hang".
Control.Invoke() will execute the delegate on the other thread also, but will cause the calling thread to wait for it to complete.
Generally in Windows Forms you are better off using Control.BeginInvoke() wherever possible to help avoid deadlocking between threads that can occur when one thread waits for another, as with Control.Invoke().
If the "document" object inherits from System.Windows.Forms.Control, you can simply call document.BeginInvoke(myDelegate).
However if it is actually some other component that encapsulates GUI controls, it may expose some way to call BeginInvoke. Check the documentation (if any). If there is no such ability, then unfortunately it is probably just not designed to support multi-threaded applications.
It looks like you are confused about the various Invoke/BeginInvoke types (understandable). This earlier question: What is the difference between Invoke and BeginInvoke? and Jon Skeets answer should help clarify things.
@Graviton, a related task with an answer is found here. The person was using BackgroundWorker to update a textbox, same concept applies (yours is only a single worker thread).
精彩评论