Skip to content

Constructs allowed in an async method

The body of work that spans frames can contain loops, branches, calls to other methods, and event sends, combined with await. Local variables keep their values across a suspension.

SyntaxDescriptionNote
while (running) { ...; await Async.Frames(60); }while loops
foreach (GameObject lamp in lamps)foreach loopsIn this example, the elements of lamps are enabled one at a time, every 5 frames.
for (y ...) { for (x ...) { ... } await Async.Frame(); }Nested loops
if (items[i] < 0) continue;break and continueAn iteration skipped by continue never reaches the await, so it consumes no frame.
if (slow) { await Async.Frames(120); } else { await Async.Frame(); }Branching with ifThe statement after the branch runs once the wait has finished, whichever branch was taken.
case 0: await Async.Frame(); done = 1; break;Branching with switchA default that contains no await does not suspend and finishes within the same frame.
if (skip) return;Return partway through
int[] work = new int[8]; await Async.Frame();Local arrays
level = Step(level);Calling a methodThe callee does not suspend; it runs to completion at the point of the call.
SendCustomEvent(nameof(Bump));Sending events
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(Chime));Network eventsOnly the sender counts the wait; the receiver runs after the network latency on top of that.
phase = 1; RequestSerialization();Updating a synced fieldOnly the owner sends the value with RequestSerialization() (Sync).

A while loop can contain await.

using UnityEngine;
using Tsukimi;
public class ParFlowWhile : TsukimiBehaviour
{
public bool running;
public int ticks;
private async FrameTask Pulse()
{
while (running)
{
ticks = ticks + 1;
await Async.Frames(60);
}
}
void Start()
{
running = true;
Pulse();
}
}

A foreach over an array can contain await.

using UnityEngine;
using Tsukimi;
public class ParFlowForeach : TsukimiBehaviour
{
public GameObject[] lamps;
private async FrameTask LightUp()
{
foreach (GameObject lamp in lamps)
{
lamp.SetActive(true);
await Async.Frames(5);
}
}
public override void Interact()
{
LightUp();
}
}

In nested loops, the whole inner loop can run in one frame, with a suspension on each iteration of the outer loop.

using UnityEngine;
using Tsukimi;
public class ParFlowNested : TsukimiBehaviour
{
public int[,] grid;
public int sum;
private async FrameTask Total()
{
for (int y = 0; y < grid.GetLength(0); y++)
{
for (int x = 0; x < grid.GetLength(1); x++)
{
sum += grid[y, x];
}
await Async.Frame();
}
}
public override void Interact()
{
Total();
}
}

break and continue work in a loop that contains await.

using UnityEngine;
using Tsukimi;
public class ParFlowBreakContinue : TsukimiBehaviour
{
public int[] items;
public int sum;
private async FrameTask Scan()
{
for (int i = 0; i < items.Length; i++)
{
if (items[i] < 0) continue;
if (items[i] > 100) break;
sum += items[i];
await Async.Frame();
}
}
public override void Interact()
{
Scan();
}
}

if and else can each contain an await with a different frame count.

using UnityEngine;
using Tsukimi;
public class ParFlowBranch : TsukimiBehaviour
{
public bool slow;
public int done;
private async FrameTask Run()
{
if (slow)
{
await Async.Frames(120);
}
else
{
await Async.Frame();
}
done = done + 1;
}
public override void Interact()
{
Run();
}
}

A case in a switch can contain await.

using UnityEngine;
using Tsukimi;
public class ParFlowSwitch : TsukimiBehaviour
{
public int mode;
public int done;
private async FrameTask Run()
{
switch (mode)
{
case 0:
await Async.Frame();
done = 1;
break;
case 1:
await Async.Frames(30);
done = 2;
break;
default:
done = 3;
break;
}
}
public override void Interact()
{
Run();
}
}

A return before the await finishes in that frame without suspending.

using UnityEngine;
using Tsukimi;
public class ParFlowEarlyReturn : TsukimiBehaviour
{
public bool skip;
public int done;
private async FrameTask Run()
{
if (skip) return;
await Async.Frames(10);
done = done + 1;
}
public override void Interact()
{
Run();
}
}

An array created locally with new keeps its contents across a suspension.

using UnityEngine;
using Tsukimi;
public class ParFlowArrayAcross : TsukimiBehaviour
{
public int total;
private async FrameTask Fold()
{
int[] work = new int[8];
for (int i = 0; i < work.Length; i++)
{
work[i] = i * i;
}
await Async.Frame();
for (int i = 0; i < work.Length; i++)
{
total += work[i];
}
}
public override void Interact()
{
Fold();
}
}

A method without [MaxTasks] can be called both before and after a suspension.

using UnityEngine;
using Tsukimi;
public class ParFlowCallMethod : TsukimiBehaviour
{
public int level;
private int Step(int v)
{
return v + 1;
}
private async FrameTask Climb()
{
level = Step(level);
await Async.Frame();
level = Step(level);
}
public override void Interact()
{
Climb();
}
}

SendCustomEvent can be called both before and after a suspension.

using UnityEngine;
using Tsukimi;
public class ParFlowSendEvent : TsukimiBehaviour
{
public int hits;
public void Bump()
{
hits = hits + 1;
}
private async FrameTask Knock()
{
SendCustomEvent(nameof(Bump));
await Async.Frames(30);
SendCustomEvent(nameof(Bump));
}
public override void Interact()
{
Knock();
}
}

SendCustomNetworkEvent can be sent after an await.

using UnityEngine;
using VRC.Udon.Common.Interfaces;
using Tsukimi;
public class ParFlowNetworkEvent : TsukimiBehaviour
{
public int chimes;
public void Chime()
{
chimes = chimes + 1;
}
private async FrameTask Delay()
{
await Async.Frames(90);
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(Chime));
}
public override void Interact()
{
Delay();
}
}

Assigning to a [UdonSynced] field and calling RequestSerialization() can be repeated with a suspension in between.

using UnityEngine;
using Tsukimi;
public class ParFlowSynced : TsukimiBehaviour
{
[UdonSynced] public int phase;
private async FrameTask Advance()
{
phase = 1;
RequestSerialization();
await Async.Frames(60);
phase = 2;
RequestSerialization();
}
public override void Interact()
{
Advance();
}
}