Jobs - break up a workload into multiple smaller units of work that then get distributed over multiple threads and operated concurrently. Think how if you have 1000 positions that need to be updated and you have 8 CPU cores available, so you divide it into 125 position updates across 8 threads to facilitate maximizing the CPU usage.
Tasks - facilitates asynchronous work flows. This does not mean that the work is therefore threaded, though threading may get involved. An example of a task that isnāt threaded is say awaiting for a web request to return. By using a task you can stop executing your logic and then return executing your logic when the web request has come back from the server. In the mean time your thread can continue on doing other work. Mind you the thing that you await COULD be another thread though. You could spin up a task on a separate thread (the task library facilitates this via its ThreadPool) and then await that tasks workload⦠but really this is all just some syntax sugar and api magic wrapped around the existing threading library allowing you to await inline rather than using things like callback delegates, waithandles, and the ilk.
Threading - the fundamental aspect of the C#/.Net library that facilitates spinning up threads within the .Net runtime (I make this distinction since unity can spin up threads independent of the .net framework since the engine is not exclusively .net). When the task library does create a thread, itās using the threading library. You can do so via something like the Task.Run method:
In the vast majority of scenarios you likely shouldnāt be using the threading library directly and instead relying on the task library to spin up threads via its ThreadPool. Or using an alternative system like jobs when applicable.
Coroutines - coroutines are a hacky unity specific thing that exists from a time before the async/tasks library being available to Unity (tasks didnāt exist when Unity was first created, and even after tasks was added to C#/.net, unity still didnāt have it because it was relegated to a very old version of .net for quite some time until the likes of IL2CPP came about). They effectively do something similar to the async/tasks library by exploiting the āiterator functionā feature of C#. It still exists to this day because of its legacy status. That vast majority of stuff you could do with Coroutines could be done with a Task.
ā¦
Which one should you use?
Well Coroutines and Threading is sort of tossed out the window IMO since Tasks is better suited to either of them. Tasks directly facilitates threading via its threadpool if needed. And Tasks effectively do very nearly everything, if not everything, that a coroutine can; if only requiring a slightly different syntax and/or logical lift.
So in the end the question is do you use Jobs or Tasks (w/ threading).
And Iād say thisā¦
Are you trying to speed it up, or are you trying to just avoid making the main thread/UI hang?
If you just want to avoid having the main thread hang. Go with a Task. If your simulation doesnāt access the unity api (which isnāt thread safe) you can just call Task.Run and do all your work on another thread. Await that task to know when itās done. If the work does require accessing the unity api though (the simulation also renders) a task is still useful⦠youāll just have to NOT us a thread on Task.Run and instead await a Task.Yield on some interval and make sure you only increment your workload of the simulation a little bit per frame (that or remove dependencies on the unity api by tokenizing your simulation in a manner that doesnāt rely on the unity api).
If you want to try to optimize it by exploiting all of the cores of your CPU⦠jobs might be useful as long as itās non-trivial to jobify your workload. Knowing if your workload is easily jobifiable though requires more information of what your workload actually entails. But if your simulation is just 1 long sequential operation (i.e. canāt be distributed concurrently), jobs wonāt really benefit you much and you can just stick with a task.