C# Technical snippets
This page was created to serve as a place for gathering different code examples regarding topics that students often wonder about.
It is intended to grow over time.
Multitasking
Sometimes reffered to simply as threading or async (for asynchronous processing), multitasking allows us to do multiple things at the same time.
By and large there are two common ways of doing this, though I am sure you can find examples of other types.
- One "actor" takes turns working on different things.
- Different "actors" working on different things in parallel.
The first case is generally known as cooperative multitasking or non-preemptive multitasking. The second is usually referred to as threading or timeshared or preemptive multitasking. (In case you wish to google more about the concepts)
Warning! While lucrative at first, the act of adding multitasking to your application creates a number of concerns with regards to shared variables, synchronisation, deadlocking and race conditions. If none of these are familiar to you, I strongly suggest you find a different way to speed up your processing. If despite this you wish to continue on this path I suggest you try to write your async code in as functional a manner as possible, trying to only use immutable inputs and data.
TL:DR Multitasking can be hard and error prone. If you MUST use it, try to avoid shared variables and write in a functional manner.
Coroutines / Cooperative multitasking
This is a form of multitasking which is NOT executed on multiple threads. Instead, the existing thread is shared between multiple ongoing tasks.
In order to ensure that a task does not block the main thread, it needs to yield voluntarily. The common problem with this type of multitasking is that a poorly written coroutine that does not yield properly can essentially block the main thread.
In the context of Unity, this type of multitasking can be useful when starting a process that needs to manipulate an object across many frames. From personal experience, I have found that this type of behavior is often better achieved with a separate script or monobehavior.
There are pretty good and complete examples of how to use Coroutines in the Unity documentation.
https://docs.unity3d.com/Manual/Coroutines.html
Links to an external site.
Threading / Asynchronous multitasking
There are many ways to create threads and manage their lifecycle.
In terms of comprehension and ease of use, I would suggest the use of a software the engineering concept known as a Task in C#. This is often called a Promise, Future, Deferred etc in other languages, but they refer to similar concepts (often interchangably, though they all have slightly different nuances in theory).
In short, a Task is an object referring to a future result which you know will exist eventually, but might or might not exist at any given point in time.
Assuming you have a method that you wish to run asynchronously
private string TaskMethod(String input)
{
...
}
In C# you can start it as a task with
Task<string> task = Task.Run(() => TaskMethod("Example"));
Then, you can access the Task's state and results with
task.IsCompleted
task.IsCancelled
task.IsFaulted
var output = task.Result
Putting all of that together, an example for running an async task in Unity might look like this
public class TaskExample : MonoBehaviour
{
private Task<string> _task;
public void FixedUpdate()
{
if (_task == null)
{
_task = Task.Run(() => TaskMethod("Banana"));
Debug.Log($"Starting new task at {Time.time}, Thread:{Thread.CurrentThread.ManagedThreadId}");
}
if (_task.IsCompleted)
{
var taskResult = _task.Result;
Debug.Log($"{taskResult} at {Time.time}, Thread:{Thread.CurrentThread.ManagedThreadId}");
_task = null;
}
else
{
Debug.Log($"Waiting for work at {Time.time}, Thread:{Thread.CurrentThread.ManagedThreadId}");
}
}
private string TaskMethod(String input)
{
Debug.Log($"Starting to work on a {input}, Thread:{Thread.CurrentThread.ManagedThreadId}");
for (int i = 1; i < 4; i++)
{
Thread.Sleep(50);
Debug.Log($"Working: {i * 50}, Thread:{Thread.CurrentThread.ManagedThreadId}");
}
return $"Hello, World! I am a async processed {input} from Thread {Thread.CurrentThread.ManagedThreadId}!";
}
}
Which produces the following output:
NB! This works asynchronously, so your log output might or might not be fully in chronological order depending on a variety of circumstances.
The above is a basic starter for you to get a feel for how Tasks work.
For more information about tasks, I recommend the .NET documentation.
Unity uses .NET 2.1 by default usually, but it can be changed.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=netcore-2.1
Links to an external site.
Once you work more with Tasks you will quickly find out about thread pools and task factories, but you dont need to know about them for simpler implementations.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool?view=netcore-2.1
Links to an external site.
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory?view=netcore-2.1
Links to an external site.