C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4/C++ で lambda-expression なタスクをお手軽に非同期処理に投げる方法

// 最小限のコード例
// グラフの意味はさておき、単発のタスクを
// とりあえずなんでもよいのでUE4のタスクプールに投げたい場合はこれだけでも動作する
FFunctionGraphTask::CreateAndDispatchWhenReady( []{}, TStatId(), nullptr );
// 解説用に関連して扱う必要のある型が分かりやすいように少し分解したコード例
TFunction< void() > f0 = []{};
FGraphEventRef t0 = FFunctionGraphTask::CreateAndDispatchWhenReady( f, TStatId(), nullptr );

// 第3引数に nullptr ではなく前提条件とする FGraphEventRef を渡すとタスクグラフが構築される
// 第4引数に ENamedThreads を明示的に与えるとスレッドプール内のどのスレッドに実行させたいか、優先度をどうするかなど設定できる
TFunction< void() > f1 = []{};
ENamedThreads p1 = AnyBackgroundThreadNormalTask;
FGraphEventRef t1 = FFunctionGraphTask::CreateAndDispatchWhenReady( f, TStatId(), t0, p1 );

// メインスレッドで実行しないとクラッシュするような処理をさせたい場合には明示的に GameThread を指定しないと
// ありがちなスレッドとコンテキストによる「たまに落ちる」的なバグを埋め込む事があるので注意。
// 例: UProceduralMeshComponent の CreateMeshSection とか
TFunction< void() > f2 = []{};
ENamedThreads p2 = ENamedThreads::GameThread;
FGraphEventRef t2 = FFunctionGraphTask::CreateAndDispatchWhenReady( f, TStatId(), t1, p2 );

// 完了チェックしたい場合
if ( t2.IsComplete() )
  somthing();

ラムダ式UE4のタスクプールに非同期で投げる方法は簡単な非同期処理の実装にお手軽で嬉しい。( C++ 標準の promise / future / thread に対応する UE4 標準の TPromise / TFuture / FRunnableThread の使い方 - C++ ときどき ごはん、わりとてぃーぶれいく☆ での実装例のようにタスクにクラスを定義する必要が無い)

Note: ただし、このお手軽な方法は「それほど重くない処理」または「ゲームスレッド以外でのんびり実行してくれればよい処理」などでは便利に使えるが、「ゲームスレッドで実行する必要があり、1フレームに近いか、あるいは1フレームを超える処理時間を要する重い処理」や「ゲームスレッドで実行する必要があり、1つあたりは0.1msで処理できるがほぼ同時に1000タスク発生する処理」のようなタスクの非同期処理には実質的に使えない。タスクの粒度によらず前提条件さえ満たしていれば溜まっているタスクを Tick 1フレームで全てビッグバン実行してしまうため、メインスレッドや描画スレッドが想定するフレームレートをこなせなくなるようなタスクを投げたい場合には、フレームあたりのタスクの実行粒度を制御する工夫が必要となる・w・

参考