Two thoughts on Jobs and Multithreading

I am studying how to use the Unity Transport and the examples all point to using Jobs. As I learn how to use Jobs, I came across the following:

Don’t try to update NativeContainer contents
Due to the lack of ref returns, it is not possible to directly change the content of a NativeContainer. For example, nativeArray[0]++; is the same as writing var temp = nativeArray[0]; temp++; which does not update the value in nativeArray.

When an operator doesn’t perform the most native or default behavior, it makes life more difficult for developers as they have to debug and then work around the problems that the new behavior introduces.

As a result of the safety features Jobs provide, I find myself writing more code than necessary to work around Jobs.

I have written plenty of multithreaded code in the past and I prefer to just use .NET’s Threading instead of Jobs. The code is straight forward to write and more easily understood when read.

Yet, it is my understanding that to use .NET’s threading could negatively impact Unity’s ability to perform well (e.g., the worker thread may interrupt the Unity thread in unexpected and undesirable ways). Is this true? Does Unity not play well with .NET threading?

Unity’s own API’s aren’t thread safe. So for example you can’t Instantiate things. You can set / get position of a Transform.

As far as I understood the overhead of .NET threading is more expensive than the C# Job System.
But yeah the requirement of jobs their data being structs you have to work your way around it.
The C# job system plays well with ECS, more than it does with GameObjects. But ECS is not production ready yet.
It is also a different way of doing things.

Keep in mind that your code is not the application.

Unity is the application.

By prior arrangement Unity will load the first scene and then begin calling Unity callbacks (such as Start(), Update(), etc.) on any of your scripts it finds in that first scene.

Your code is merely a side note that Unity MIGHT execute, assuming you meet the arrangement.

You may only transact against most Unity methods from this one single thread. Every other thread needs to marshal data to the main thread for updating to the Unity API.

1 Like

My project used to be multi threaded with c# threads. I got sick of the unpredictability where sometimes my threads would appear to hang for a long time and I could never track down why. It could have been my code but it did feel like I was fighting Unity. After taking the plunge and replacing everything with jobs everything was far more stable and never had the hands again, although speed was similar whenever my old threads system did run.

Then I turned on Burst and got huge performance gains along side the stability.

Bottom line, it might have taken a lot of work but I’m really glad I ditched C# threads

1 Like

Interesting…

I decided to go this route myself to see if the Jobs architecture will be easy to adjust to. I am trying to implement the Unity Transport using the documented sample code as a starting point. I found that when the client connected to the server, it wound up in an infinite loop with a Connect state, while the sample shows the loop ending on Empty state. I wasn’t processing the Connect state, nor did I want to as the sample code did, and the server isn’t closing the connection. (I don’t want it to close, but perhaps that is the issue?).

This suggests that the Job hung the main thread in JobHandle.Complete(). I don’t know of any way to test the JobHandle for completion without blocking. There should be a way, ya?

EDIT: It isn’t crashing, it is hanging, if not the server side receiving, then the client side. In each case it thinks there is data to read, and I may not be reading it out.

JobHandle.Complete() blocks the main thread until the job has been completed. If you just want to check if the job has completed you can check the value of JobHandle.IsCompleted.

https://docs.unity3d.com/ScriptReference/Unity.Jobs.JobHandle.IsCompleted.html

Ahh sorry, ok we are talking about two different things! I’m talking about using Jobs for long running threads doing a ton of work, not for waiting to check a server state. I’ve not tried to do that with jobs yet and missed you mentioned Transport.

As Ryiah says, calling Complete gets the job/thread to finish what it started on the main thread so it could be your issue if your doing any of the usual while(true), check for response, thread.sleep.

I am thinking now I want to just roll my own socket code and use Threading instead. I think synchronizing between the threads should be straight forward and the only thing I will transfer between the threads is data and communication settings. I am looking at this due to issues I am finding with Transport (and conflicts with the samples and documentation, but those are for another thread in another part of the forum I suppose). But on this topic here, do you think that the overhead of thread context switching for a socket communications layer with Unity will be an issue, seeing how hundreds of other threads on a PC are also competing with a Unity game application? I am wondering if there is something I may not be aware of?

EDIT: For anyone who may be interested in my question above…

After implementing my own RUDP using threading, it works just fine, no issues appear due to context switching or anything else. It uses one thread that waits for data to go out and another thread waiting for data to come in. Because it uses UDP listening port, the server can receive from the one thread any number of client communications.

For TCP, I am using .NET thread pools and async .NET calls for socket calls. This allows any number of socket connections on the server to block in their own socket receive calls, while not committing to consuming one thread for each connection.

It all seems to work really well.

1 Like