コンテンツにスキップ

async / await

1 フレームに収まらない処理は、async FrameTask のメソッドに書きます。メソッドは await の位置で中断して Udon へ戻り、後のフレームで続きから再開します(並列には実行されず、同時に実行できるのは既定で 1 本です)。

書き方説明注意
async FrameTask M()中断と再開呼び出し元は完了を待たず、呼び出しの次の文へ同じフレームのうちに進みます。
await Async.Frames(30)フレーム数の指定待つ単位はフレームだけで、秒では待てません。
[MaxTasks(4)]同時に実行できる本数属性は本数を書くときだけ付けます(書かなければ 1 本です)。それぞれが自分の引数と途中の値を持ち、別々に進みます。本数ぶんが全部埋まっているときの呼び出しは始まりません。⚠ 本体は本数のぶん複製されるので、プログラムはその数だけ大きくなります。
FrameTask t = M(); if (!t.Started)開始の確認前の実行が終わっていない間の呼び出しは始まらず、Started は false になります。
if (i % 256 == 255) await Async.Frame();ループの分割ループ変数 i と途中の合計は、中断をまたいで値を保ちます。
async FrameTask Fade(int frames, int target)引数の受け渡し引数 target は、await の後でも呼び出し時の値のまま読めます。
await Async.Frames(speed * 2 + 1)フレーム数の式式は await に到達した時点で評価されます。
private static async FrameTask Tick()static のメソッド
await Fade(30)ほかの跨ぐメソッドを待つ相手がまだ実行中だったときは、終わるのを待ってから始めさせます(await を付けずに呼ぶ形とはここが違います。あちらは始まりません)。相手が 1 度も中断しなければ、フレームをまたがずにそのまま続きへ進みます。
async FrameTask Start()イベントを跨がせるUdon はこれまでどおり Start を呼び、中断した続きは後のフレームから再開します。フィールド初期化子は本体より先に実行されます。毎フレーム呼ばれるイベント(Update など)を跨がせると、2 回目以降は前の実行が終わっていないため始まりません。Interact() のように基底から継ぐイベントは、C# の規則で戻り値を変えられないため跨がせられません。
書き方説明エラー理由代替
async void M()戻り値が無いので、始まったかを読めないTUKI0001設計async FrameTask にする
[MaxTasks(0)]本数に 1 未満を書くTUKI0001設計1 以上を書く
[MaxTasks(100000)]本数に上限より大きい数を書くTUKI0001設計本数を減らす(本体が複製されるので、プログラムがその数だけ大きくなります)
await 本数が 2 以上のメソッド()本数を増やしたメソッドの完了を待つTUKI0001設計await を付けずに呼ぶか、待ちたい側の本数を 1 にする
A() で await B()・B() で await A()待つメソッドどうしが互いを待つTUKI0001設計await を付けずに呼ぶか、輪を切る
async FrameTask M<T>()型引数を取るTUKI0001未実装型引数を使わない形に書き直す
Async.Frame();待ちを await なしで書くTUKI0001設計await を付ける
FrameTask t = M(); await t;変数へ受けた待ち受けを待つTUKI0001設計await M() と、呼びをそのまま待つ
yield return少しずつ値を返すTUKI0001環境フレームを跨ぐなら async と await、値を少しずつ返すなら配列に詰める

本体に書ける構文はasync メソッドの中に書ける構文、GPU との組み合わせはGPU カーネルと awaitにあります。

await Async.Frame() を書いた位置で中断し、続きは次のフレームから再開します。

using UnityEngine;
using Tsukimi;
public class ParAsyncFrames : TsukimiBehaviour
{
public int total;
private async FrameTask Sum()
{
int running = 0;
for (int i = 0; i < 1000; i++)
{
running += i;
if (i % 128 == 127) await Async.Frame();
}
total = running;
}
void Start()
{
Sum();
}
}

Async.Frames(n) は、指定した数のフレームを待ってから再開します。

using UnityEngine;
using Tsukimi;
public class ParAsyncWaitMany : TsukimiBehaviour
{
public int step;
private async FrameTask Countdown()
{
step = 3;
await Async.Frames(30);
step = 2;
await Async.Frames(30);
step = 1;
await Async.Frames(30);
step = 0;
}
void Start()
{
Countdown();
}
}

属性に数を書くと、同じメソッドをその数まで同時に実行できます。

using UnityEngine;
using Tsukimi;
public class ParAsyncSeats : TsukimiBehaviour
{
public int finished;
[MaxTasks(4)]
private async FrameTask Petal(int frames)
{
await Async.Frames(frames);
finished = finished + 1;
}
public override void Interact()
{
Petal(10);
Petal(20);
Petal(30);
Petal(40);
}
}

戻り値の Started で、その呼び出しで処理が始まったかを読めます。

using UnityEngine;
using Tsukimi;
public class ParAsyncSeat : TsukimiBehaviour
{
public int done;
private async FrameTask Once()
{
await Async.Frames(10);
done = done + 1;
}
public override void Interact()
{
FrameTask task = Once();
if (!task.Started) return;
}
}

256 周ごとに await Async.Frame() を置くと、1 フレームに進むのは 256 周分になります。

using UnityEngine;
using Tsukimi;
public class ParAsyncLoopSplit : TsukimiBehaviour
{
public int[] board;
public int score;
private async FrameTask Evaluate()
{
int sum = 0;
for (int i = 0; i < board.Length; i++)
{
sum += board[i] * board[i];
if (i % 256 == 255) await Async.Frame();
}
score = sum;
}
public override void Interact()
{
Evaluate();
}
}

フレームを跨ぐメソッドは引数を取れます。

using UnityEngine;
using Tsukimi;
public class ParAsyncArgs : TsukimiBehaviour
{
public int level;
private async FrameTask Fade(int frames, int target)
{
await Async.Frames(frames);
level = target;
}
public override void Interact()
{
Fade(30, 100);
}
}

待つフレーム数には式を書けます。

using UnityEngine;
using Tsukimi;
public class ParAsyncComputedFrames : TsukimiBehaviour
{
public int speed;
public int done;
private async FrameTask Wait()
{
await Async.Frames(speed * 2 + 1);
done = done + 1;
}
void Start()
{
speed = 10;
Wait();
}
}

フレームを跨ぐメソッドは static にもできます。

using UnityEngine;
using Tsukimi;
public class ParAsyncStatic : TsukimiBehaviour
{
private static int ticks;
private static async FrameTask Tick()
{
await Async.Frame();
ticks = ticks + 1;
}
void Start()
{
Tick();
}
}

自分の跨ぐメソッドを await すると、そちらが終わってから続きが実行されます。

using UnityEngine;
using Tsukimi;
public class ParAsyncAwaitAsync : TsukimiBehaviour
{
public int step;
private async FrameTask Fade(int frames)
{
await Async.Frames(frames);
}
private async FrameTask Sequence()
{
step = 1;
await Fade(30);
step = 2;
await Fade(30);
step = 3;
}
public override void Interact()
{
Sequence();
}
}

Unity のイベントそのものを跨ぐメソッドにできます。

using UnityEngine;
using Tsukimi;
public class ParAsyncEntry : TsukimiBehaviour
{
public int step;
private async FrameTask Start()
{
step = 1;
await Async.Frames(30);
step = 2;
await Async.Frames(30);
step = 3;
}
}