Jump to content

BackgroundWorker runs RunWorkerCompleted handler on the wrong thread


Recommended Posts

Is BackgroundWorker safe to use in KSP mods? I'm using it to offload intensive calculations, but I need it to trigger a UI update when done, and whenever I do this by any means other than setting a bool that I check later in the main thread's Update(), the whole game crashes. Including via the RunWorkerCompleted event.

Quote

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.

I tried logging Thread.CurrentThread.ManagedThreadId, and that seemed to confirm what I suspected, that it's running the RunWorkerCompleted handler on a background thread, sometimes the worker thread, sometimes a completely new thread:

Quote

[LOG 13:13:09.679] [Astrogator 036.738] Background worker created by thread 1
[LOG 13:13:09.679] [Astrogator 036.738] Background worker started by thread 1
[LOG 13:13:09.680] [Astrogator 036.738] Background worker running on thread 2
[LOG 13:13:09.686] [Astrogator 036.745] Background worker completion handled on thread 3
[LOG 13:13:15.342] [Astrogator 042.400] Background worker created by thread 1
[LOG 13:13:15.342] [Astrogator 042.400] Background worker started by thread 1
[LOG 13:13:15.342] [Astrogator 042.400] Background worker running on thread 2
[LOG 13:13:15.342] [Astrogator 042.401] Background worker completion handled on thread 2
[LOG 13:13:19.419] [Astrogator 046.478] Background worker created by thread 1
[LOG 13:13:19.419] [Astrogator 046.478] Background worker started by thread 1
[LOG 13:13:19.420] [Astrogator 046.478] Background worker running on thread 4
[LOG 13:13:19.420] [Astrogator 046.479] Background worker completion handled on thread 4

I expected each of those bold red thread IDs to be 1, since that's the thread that kicked off the worker. Are there any known tricks that will make this work the way the C# documentation says it does? People on stackoverflow are talking about SynchronizationContext.Current, which is null the very first time but non-null after that, but setting it to a new instance of SynchronizationContext doesn't change the above output at all.

Link to comment
Share on other sites

No, BackgroundWorker won't work properly with Unity because BW is a ComponentModel class and Unity does not support ComponentModel.  BW needs the infrastructure provided by WInForms or ASP.NET that lets it (and other such classes) say to the main thread "pardon me, I need you to call this method next time it's convenient".

You can prove this:  Try a BW in a WinForms app like the one in the MSDN help and it'll work as documented, but try it in a plain Console app and the result will be the same as you got.

So I'd go with the bool flag and check on Update().  Or if you can live with a bit of UI update latency you can spawn a Unity coroutine that only checks every 1/10th of a second (or whatever delay you can live with).  In either case, make sure to use lock whenever you change the flag.

 

Link to comment
Share on other sites

29 minutes ago, paulprogart said:

No, BackgroundWorker won't work properly with Unity because BW is a ComponentModel class and Unity does not support ComponentModel.  BW needs the infrastructure provided by WInForms or ASP.NET that lets it (and other such classes) say to the main thread "pardon me, I need you to call this method next time it's convenient".

You can prove this:  Try a BW in a WinForms app like the one in the MSDN help and it'll work as documented, but try it in a plain Console app and the result will be the same as you got.

So I'd go with the bool flag and check on Update().  Or if you can live with a bit of UI update latency you can spawn a Unity coroutine that only checks every 1/10th of a second (or whatever delay you can live with).  In either case, make sure to use lock whenever you change the flag.

Thanks! Kind of annoying that they ship a component with a limitation like this and make it look like a generic utility that does exactly what I want (and it doesn't even throw exceptions when its prerequisites aren't met).

Good shout on the lock.

3 minutes ago, sarbian said:

Or you can use something that will call your UI event on the main thread. 

Look for dispatcher in my MechJeb code

https://github.com/MuMech/MechJeb2/tree/master/MechJeb2/UnityToolbag/Dispatcher

Yeah, I found a similar class that I'm hoping to use to craft my own UnityBackgroundWorker...

How do you make Unity call Update on a MonoBehaviour? Mine never runs. Am I supposed to register it somewhere?

Link to comment
Share on other sites

31 minutes ago, sarbian said:

You need to create a game object and add the monobehaviour to it. Look at the last method in my code 

... so it has to be a singleton, and I can't use a parameterized constructor. Never mind about the UnityBackgroundWorker, I'm just going to set that bool and be done with it.

Link to comment
Share on other sites

hum, I don't understand the problem. When your mod start you call CreateDispatcher to make sure it is up (My call could use some love to check if it s already there). In your background code whenever you need to call unity objects you just call InvokeAsync (or Invoke) on the singleton. Nothing complex and it works fine.

I do it in MJ to log from a thread :

Dispatcher.InvokeAsync(() => Debug.LogException(ex));

 

Link to comment
Share on other sites

On 20/2/2017 at 8:43 PM, HebaruSan said:

 

Is BackgroundWorker safe to use in KSP mods? I'm using it to offload intensive calculations, but I need it to trigger a UI update when done, and whenever I do this by any means other than setting a bool that I check later in the main thread's Update(), the whole game crashes. Including via the RunWorkerCompleted event.

 

If you create a new thread, I've noticed that if that thread call a coroutine, then that coroutine is executed on the main thread. Maybe that could help you.

So I'm thinking something like calling a coroutine when the calculation in the thread finishes.

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...