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

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

C++ 標準の promise / future / thread に対応する UE4 標準の TPromise / TFuture / FRunnableThread の使い方

C++ 標準の std::promise / std::future / std::thread

C++erにとっては基礎的なおさらい。

#include <iostream>
#include <future>

int main()
{
  using namespace std;
  
  promise< int > p;
  auto f = p.get_future();
  auto t = thread( [ =, p = move(p) ] () mutable { p.set_value( 123 ); } );
  cout << f.get();
  t.join();
}

C++ 標準では lambda-expression を std:thread へ放り投げるのが簡単なところが嬉しいポイント。

対応する UE4 標準の TPromise / TFuture / FRunnableThread

UE4 標準でコード上で同様の事を記述しようとすると少しめんどくさい。

#include "Runtime/Core/Public/Async/Future.h"

void your_something_function()
{
  // std::promise と同様
  TPromise< int > p;
  // std::future の std::promise からの取得と同様
  auto f = p.GetFuture();
  // ここがちょっとめんどくさい: std::thread( ... ) に相当するコード
  struct r_type: public FRunnable
  { TPromise<int> p;
    r_type( TPromise<int>&& p_ ) : p( std::move( p_ ) ) { }
    bool Init() override { return true; }
    uint32 Run() override { p.SetValue( 123 ); return 0; }
    void Stop() override { }
  } r( std::move( p ) );
  // ↓この2つのフラグは今回のミニマルコード例を応用する人がいた場合に間違えるととても危険なため目立つようにこのように書いておきました。
  constexpr auto bAutoDeleteSelf      = false;
  constexpr auto bAutoDeleteRunnable  = true;
  // ↓これに↑をやる lambda-expression をそのまま放り込めないので FRunnable の派生クラスを定義してもにょもにょ。
  FRunnableThread::Create( &r, TEXT( "promise-future tester"), bAutoDeleteSelf, bAutoDeleteRunnable, 0 );
  // std の例では cout していた部分を今回コード例では UE_LOG で代用しました。
  UE_LOG( LogTemp, Log, TEXT( "promise-future: %d" ), f.Get() );
}

ミニマルコード例として C++ 標準の promise / future / thread ( / lambda-expression ) に対応する事を優先して UE4 標準のコードを書くとこうなります。( FRunnableThread::Create には引数が少し簡単なオーバーロードもあり、 r_typenew して bAutoDeleteSelf 相当のフラグを true として構わない状況ではそちらを使った方が楽な事もあるかもしれません。 )

実用上はこのような例では PROMISE-FUTURE-THREAD 系の低レベルの記述をせず、 C++ 標準なら std::async 、 UE4 標準なら Async へそれぞれ lambda-expression を放り投げた方がよい場合が多いと思いますが、ときおり諸事情により PROMISE-FUTURE 系の低レベルのコード記述が欲しい事があります。例えば既存のライブラリーが非同期処理のコールバックパターンの API を提供しているところで、外で PROMISE と FUTURE を作って非同期処理を呼びつつコールバックに PROMISE を std::move で投げておいてコールバック内で PROMISE に値をセットさせ API の呼び出し元では FUTURE から値の取得を行ったり FUTURE の状態を監視するなどしたい場合とか。

参考