Knowledge Garden

Search

Search IconIcon to open search

C Sharp Async

Last updated Oct 31, 2022 Edit Source

https://www.youtube.com/watch?v=WY-mk-ZGAq8

Async is better than coroutins for a few reasons. Chaining coroutines becomes very messy.

# convert coroutine to async

1
2
3
4
5
6
7
8
9
public IEnumerator RotateForSeconds(float duration)
{
	var end = Time.time + duration;
	while (Time.time < end)
	{
		transform.Rotate(new Vector3(1,1)*Time.deltatime * 150);
		yield return null;
	}
}
1
2
3
4
5
6
7
8
9
public async void RotateForSeconds(float duration)
{
	var end = Time.time + duration;
	while(Time.time < end)
	{
		transform.Rotate(new Vector3(1,1)*Time.deltatime * 150);
		await Task.Yield();
	}
}

chaining coroutines suck

achieving sequential actions with async is much easier.

with async we can return a Task (similar to a promise in javascrip)

if its async monitor how long it’s elapsed

1
2
3
4
5
6
7
8
9
public async Task RotateForSeconds(float duration)
{
	var end = Time.time + duration;
	while(Time.time < end)
	{
		transform.Rotate(new Vector3(1,1)*Time.deltatime * 150);
		await Task.Yield();
	}
}

C# knows you want to return a task so there’s no need for a return statement

coroutine version

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ShapeManager : MonoBehaviour 
{
	[SerializedField] private Shape[] _shapes;
	
	public void BeginTest()
	{
		for(var i = 0;i < _shapes.Length; i++)
		{
			_shapes[i].RotateForSeconds(1+1*i); //
		}
	}
}

async version

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ShapeManager : MonoBehaviour 
{
	[SerializedField] private Shape[] _shapes;
	
	public async void BeginTest()
	{
		for(var i = 0;i < _shapes.Length; i++)
		{
			await _shapes[i].RotateForSeconds(1+1*i); //added await
		}
	}
}

what if we want to run it syncronously but we want to make sure they are all done before we continue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class ShapeManager : MonoBehaviour 
{
	[SerializedField] private Shape[] _shapes;
	[SerializedField] private GameObject _finishedText;
	
	public async void BeginTest()
	{
		_finishedText.SetActive(false);
		
		
		var tasks = new Task[_shapes.Length];//task array stores all tasks to wait for
		for(var i = 0;i < _shapes.Length; i++)
		{
			await _shapes[i].RotateForSeconds(1+1*i); //added await
		}
		
		
		await Task.WhenAll(tasks);
		
		_finishedText.SetActive(true);
	}
}

separate syncronous and asyncronously

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ShapeManager : MonoBehaviour 
{
	[SerializedField] private Shape[] _shapes;
	[SerializedField] private GameObject _finishedText;
	
	public async void BeginTest()
	{
		_finishedText.SetActive(false);
		
		
		await _shapes[0].RotateForSeconds(1+1*0);
		
		var tasks = new Task[_shapes.Length];//task array stores all tasks to wait for
		for(var i = 0;i < _shapes.Length; i++)
		{
			await _shapes[i].RotateForSeconds(1+1*i); //added await
		}
		
		
		await Task.WhenAll(tasks);
		
		_finishedText.SetActive(true);
	}
}

# Async functions can return data

1
2
3
4
5
6
async Task<int> GetRandomNumber()
{
	var randomNumber = Random.Range(100,100);
	await Task.Delay(randomNumber);
	return randomNumber;
}

so to use unity units in an async do this

1
(int)(delay*1000)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public async void BeginTest()
{
	var randomNumber = GetRandomNumber();
	
	print(randomNumber);
}


async Task<int> GetRandomNumber()
{
	var randomNumber = Random.Range(100,100);
	await Task.Delay(randomNumber);
	return randomNumber;
}

# because the above didn’t await it return the task function instead of the value

use the -is- keyword to check on the task

1
2

randomnumnber.is---

fixed version with the await function

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public async void BeginTest()
{
	var randomNumber = await GetRandomNumber(); //await
	
	print(randomNumber);
}


async Task<int> GetRandomNumber()
{
	var randomNumber = Random.Range(100,100);
	await Task.Delay(randomNumber);
	return randomNumber;
}

If you are trying to call an async function not from an async you cant use the -await- keyword, there are alternatives.

1
2
3
4
5
6
public async void BeginTest()
{
	var randomNumber = await GetRandomNumber().GetAwaiter().GetResult();
	
	print(randomNumber);
}

another way that looks clear but there is a catch

1
2
3
4
5
6
public async void BeginTest()
{
	var randomNumber = await GetRandomNumber().Result;
	
	print(randomNumber);
}

the catch: if you have a try catch loop in the async function you are trying to call it will just give you a generic error

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
async Task<int> GetRandomNumber()
{
	try {
		
	}
	catch(Exception e) {
		console.WriteLine(e);
		throw;
	}
}

There are certainly other important caveats to mention with async. You were constantly getting NREs in the background. This being caused by the fact that tasks do not respect Unity Object lifetimes. Coroutines will stop when the object that started them is destroyed. Tasks will continue on regardless, and this means that they can continue outside of playmode, and through scene changes. To counter this you need to use cancellation tokens, and they’re somewhat difficult to grasp. In reality almost every task you start should really take a cancellation token. Not only this, but there are situations you can get yourself into that completely silence exceptions occurring within a task-returning method. If you do not await a task it will not appropriately throw exceptions created within it. This can make bugs difficult to track down, and if you’re not familiar with that fact it’s a minefield.