BackgroundWorker :: GetData() Exception (Operation not called on UI thread.)

Hi,

I’m trying to use BackgroundWorker to preproses my 2D png grapichs. I do this for runtime atlassing based upon GL.MAX_TEXTURE_SIZE and making Pixel coliders. This works on Windows but on Android the RunWorkerCompletedEventHandler() is fired premature because of the following exception.

System.InvalidOperationException: Operation not called on UI thread. at Microsoft.Xna.Framework.Threading.EnsureUIThread () [0x00007] in <8335ffb25c73411d8c018e841c87a942>:0 at Microsoft.Xna.Framework.Graphics.Texture2D.PlatformGetData[T] (System.Int32 level, System.Int32 arraySlice, Microsoft.Xna.Framework.Rectangle rect, T[] data, System.Int32 startIndex, System.Int32 elementCount) [0x00000] in <8335ffb25c73411d8c018e841c87a942>:0 at Microsoft.Xna.Framework.Graphics.Texture2D.GetData[T] (System.Int32 level, System.Int32 arraySlice, System.Nullable1[T] rect, T[] data, System.Int32 startIndex, System.Int32 elementCount) [0x00011] in <8335ffb25c73411d8c018e841c87a942>:0
at Microsoft.Xna.Framework.Graphics.Texture2D.GetData[T] (System.Int32 level, System.Nullable1[T] rect, T[] data, System.Int32 startIndex, System.Int32 elementCount) [0x00000] in <8335ffb25c73411d8c018e841c87a942>:0 at Microsoft.Xna.Framework.Graphics.Texture2D.GetData[T] (T[] data) [0x0000e] in <8335ffb25c73411d8c018e841c87a942>:0 ...

Notice: Threading.EnsureUIThread, And It is NOT on the UI thread. Yet, LoadFromStream() does not trigger.! (So settings it seems fine, but getting it is a big no-no). Also Windows does mind me doing this. I have not tested Mac or IOS yet.

I’m not drawing or doing anything particular interesting when this happens. Basically I need a way to pre-process my PNG images on runtime. I can do this on the main (UI) thread, but then no cool progress indicator :disappointed:

I’m targeting: Windows, Windows Store, Android, Mac, IOS. System.Drawing - which was my first choice - is not supported everywhere, so i’m using. FromStream, GetData, SetData to play around with the pixels.

Anyone has any ideas on this…? Thanks!

EDIT: Both Mac and IOS give the same behavior. I’m assuming that its a OpenGL directive. So you cant use GetData, and probably not SetData on a separate thread on other targets then Windows or Windows Store.

Hmm I’m not sure. I don’t really have much experience with threading on Android. From what you’re describing though, my only suggestion would be to ensure you are on the UI thread by setting a flag when you are ready to do that operation, then checking for it in the main loop and performing the operation directly.

I mean, it takes time out of your main loop, but I’d imagine having a thread run on the UI thread as you would expect your code to do would amount to the same thing anyway…?

The hole point of a thread is that they unique sub-processes, so threads don’t run on threads (they run sideways). Also, I do understand that the problem is gone when running it on the main (UI) thread. But I have a hole batch of these operations. I cannot just do them all on the UI thread without it freezing up.

Why not suggest the use something from System.Collections.Concurrent? You can then just pass the data instead of building a own implementation to do this.

Honestly, most developers can figure most things out what you are refering to… I don’t see much help in it.

I could on the main thread just do a couple each update and then draw, then repeat until done. This is sometimes referred to as “Green Threading”. A downside to this is that you have to wait for the next update (frame) each time you release the process. This can add some extra time.

Other options is to find a 3th party PNG library that can convert my images to int or color buffs. But since .net is build to be comprehensive (you don’t usually need 3th party stuff) in these fields, there are not that many made.

Still MonoGame is inconsistent with GetData over its targets, and it’s up to me to deal with it. Windows is my development environment and I first build/test there. So a note to myself is to check the functionality and availability of new code before kind of finishing it. I already took a wrong turn once on System.Drawing.* which is not available on all targets, but I did not see this one coming.

Here’s what I do with stuff like this. Create a Queue<Action> field in your game class and a function that lets you enqueue actions to this queue.

private Queue<Action> _actions = new Queue<Action>();

public void EnqueueAction(Action action)
{
  if(action == null) return;
  _actions.Enqueue(action);
}

Then, at the start of your Update(GameTime) method, do this:

while(_actions.Count > 0)
  _actions.Dequeue().Invoke();

This allows you to queue up a list of things for the game to do on the UI thread. You can call Enqueue from another thread, like this:

game.Enqueue(() => {
  Console.WriteLine("La la laaaa...");
});

and it’ll run that code the next frame, on the UI thread.

I’m assuming you’re trying to grab pixel data from a Texture2D on one thread from another, so this’ll help out a lot. In the thread that’s doing the post-processing, instead of calling Texture2D.GetData() directly, do this.

//Create a manual reset event that will block this thread until it is signalled by another.
var textureDataGathered = new ManualResetEvent(false);

//Allocate memory for the texture data.
byte[] myTextureData = new byte[(myTexture.Width*4)*myTexture.Height];

//Enqueue some code to run on the next frame that gets us our texture data.
game.Enqueue(() => {
  myTexture.GetData(myTextureData);
  //Signal our non-UI thread that it's okay to continue executing.
  textureDataGathered.Set();
});

//Halt our non-UI thread until the UI thread gives us the signal.
textureDataGathered.WaitOne();
//Reset the signal in case we need to wait for something else later.
textureDataGathered.Reset();

//Do fancy stuff with our texture data.

The use of ManualResetEvent in this case prevents a possible race condition where occasionally the non-UI thread will try to access the texture data before the UI thread gets a chance to gather it. If ManualResetEvent.Set(); is called before WaitOne(); gets a chance, it’ll simply not block the thread - because there’s nothing to wait for. If not, it’ll block the thread until Set(); is called by the UI, keeping everything in sync.

Hopefully that helps. Always use that pattern if you need to access a texture or other thread-unsafe resource in MonoGame. This is the MonoGame equivalent of Windows Forms’ Control.Invoke(delegate) method.

1 Like

I don’t mean do all of them at once. Just do a couple at a time every iteration until the whole process is done. Do what you can on the background thread but save those bits that you absolutely must do on the UI thread and give up some time slices.

I wasn’t suggesting building an implementation of anything… but if you’re more comfortable using a thread-safe collection instead of a lock and a flag, by all means, your preference!

The intent though is that it sounds like you’re going to have to do some work on the UI thread. This is common for graphical stuff. I looked up why years ago, but I can’t remember anymore. I used to run into this all the time doing WinForms coding at my previous job. The approach was the same… do all the work you can on the background thread and then, for the last step of creating the resource, do it on the UI thread. Don’t wait for anything, just check to see if something is ready, do a small time slice of the work available, and then do the rest of your processing. What @Watercolor_Games wrote in their reply is actually spot on what I was thinking :slight_smile:

And yea, it sucks because you do have to take time away from your main thread to achieve this, but as far as I know this is how it’s gotta be. You’re right though, it is weird that MonoGame handles this differently on different targets. I can’t explain this (maybe a dev can), all I can do is try to suggest alternatives that may help unblock you. It sounded like that’s what you were looking for in your OP, perhaps I was mistaken?

Either way, good luck!

1 Like

The main reason that this stuff fails when you try to access texture data across threads is…preventing race conditions, really. Same reason two programs are never allowed to access the same file at the same time. There’s no guarantee (as far as MonoGame is concerned) that the two threads are in sync, so, instead of causing a more cryptic exception at a lower level when something INEVITABLY breaks, it just stops you right there and goes “Whoa, you can’t do that.”

Thanks for all the feedback!

I understand, it’s just a shame that the Windows DX version of MonoGame does allow this behavior (I was assuming it was Thread Safe). Its literally frustrating to have something working on one system, be happy, and then see it fail on an other systems. It would have speed up my development if the exception was consistent!. Also FromStream() does not complain! :expressionless:

Anyways I will reconfigure my code to properly deal with this… Since it’s initialization code I just might only use the main (UI) thread and set it up like this.

public bool render(float release = 0.02f) { ... }

I can call render in Update() to do the work and release is the progress of a step (just add up all processes and multiply it by the release to have the amount per call). In this case 1 / 0.02 = 50 steps total. The render will break on each step so that update can continue and draw is called. This can give me a window to update the progress indicator and prevent freezing. Because release is a variable i can always play around with it. If render returns true I can start the game. On a 60fps configuration this will give me 50/60 (0.83) seconds overhead which for startup seems tolerable. Also I’ve noticed that the extra threads are slower then the main (UI) thread. It’s probably configurable by setting the Thread priority, but I’m weary if it will work on all platforms. So it won’t surprise me if i’m faster setting it up like this.

But I just might still use a second thread and do something similar to what @Watercolor_Games has suggested. So thanks for all the feedback.

Old thread, but I’m suddenly encountering this issue when trying to port my game to Linux. I do loading on a separate thread and have a number of calls to Texture2D.GetData on there. Anyone have any insights as to why DirectX can call Texture2D.GetData on non-UI threads but OpenGL can’t?

For what it’s worth, the DirectX version of the game runs just fine on Proton.

There are 23 places where BlockOnUIThread should be used in MonoGame for multithreaded graphics tasks like texture loading, VertexBuffer.GetData, etc. However BlockOnUIThread is missing from Texture2D.GetData.

I raised a PR back in 2021 to fix this omission but it still hasn’t been accepted yet.

1 Like

That PR should have been accepted; it’s a pretty simple change and it shouldn’t break anyone’s game since the current behavior is to just throw an exception on a non-UI thread. I worked around my issue by doing any loading that required GetData synchronously in the LoadContent method before the loading thread starts, but it would have been nice not to need to make that change. DesktopGL should have feature parity with and identical behavior as WindowsDX, as much as is possible, IMO. Though it seems like the MonoGame team wants to move to Vulkan.