views:

369

answers:

7

Is BackgroundWorker in c# Thread Safe?

The reason I ask this is because I get a

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;
}
+1  A: 

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.

Mehrdad Afshari
@Mehdrad, I need a backgroundworker to keep the UI responsive, which *is* why it makes sense.
Ngu Soon Hui
@Ngu Soon Hui: Of course, if you have a long running task that's not just UI operations, you'll use that. If *all* you're doing in the `DoWork` method is to manipulate UI objects, you shouldn't use the `BackgroundWorker` and the reason is you'll ultimately have to do all those UI operations on the UI thread.
Mehrdad Afshari
@Mehrdad: It can make sense and it might be more subtle. The thread might just notify the "UI thread" that "something new happened", and then it's up to whatever notification handler the UI has to update the UI if needed.
Johann Gerell
@Johann: I know. In that case *if all you're doing is UI operations* will be false, you know. I'm saying this because apparently the code that OP posted is simply calling a method on a control which should be done using `Invoke` and `Invoke` simply runs the method on the UI thread, so it's pointless to use a `BackgroundWorker` here.
Mehrdad Afshari
A: 

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.

Aggelos Mpimpoudis
+1  A: 

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.

Noel Kennedy
How does "make use of the bgw's progress event which comes back on the UI thread" solve my problem, namely, being able to have a responsive UI?
Ngu Soon Hui
perhaps this is a missunderstanding of what BGW does for you. If you are doing a lot of manipulation of UI controls and this is therefore slow, and causes your UI to pull a whitey or appear to freeze, bgw can't help you! However, if you are doing something else (other than touching ui controls), for example going to a slow webservice or the DB, then doing this on the bgw will allow your ui to remain responsive. For example, you can do intensive operation x, then use the progress event to report back to your UI to say operation x completed, here are the interim results, doing operation y next
Noel Kennedy
+4  A: 

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.

GraemeF
I tried this piece of code in my DoWork event, and it still doesn't work: `this.Invoke(document.Open)`
Ngu Soon Hui
You're still calling the wrong method. If `document` is a Windows Forms control then you would call `document.Invoke(MyMethod)` where `MyMethod` is a delegate that calls `document.Open`. Depending on the signature you may be able to call `document.Invoke(document.Open)` directly.
GraemeF
@GraemeF, what if `document.Invoke` not available?
Ngu Soon Hui
Then it's not a control. Can you just tell us what `document` is?
GraemeF
It's just a normal class from third party library, sorry for the confusion
Ngu Soon Hui
OK, so if that is not a control then you can call Invoke on another control or window and it will have the same effect; the method will be called on the UI thread. In other words, you need *something* related to the UI in order to make the call.
GraemeF
@ GraemeF, I tried that, but now the form just freezes there
Ngu Soon Hui
+1  A: 

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.

Will Eddins
A: 

@Ngu Soon Hui, 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).

Jamie Altizer
Yes sure it does, see my updated question. I found that both thread hanged after applying the technique specified in your link.
Ngu Soon Hui
It appears that you have not put the InvokeRequired check in place and making BeginInvoke() call as displayed in the example. The InvokeRequired ensures that you are in the correct thread, and the call with BeginInvoke() is basically calling back into the current method. I understand that document does not offer the Invoke methods so use the form object (which may be what you are doing when you call Invoke() in your second code example). Just remember, it is one thing to call BeginInvoke()... the method needs to continue to call BeginInvoke() until InvokeRequired is false.
Jamie Altizer
A: 

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.

Ash
I understand, but when I called `BeginInvoke`, the thread hosting the `document` and another thread which launches a moving dialog box hanged.
Ngu Soon Hui