はじめに
自分の復習を兼ねて、Unity、C#のいろいろなメソッドの呼び出し方まとめ
同期呼び出し
通常の関数(メソッド)コールのような同期的な呼び出し方です。
ここでいう同期的とは「action.Invoke();」などでメソッドを実行した場合に、そのメソッドの処理が終わるまで次の処理に移行しないということを指します。
普通にメソッドを呼ぶ
一般的な書き方です。
using UnityEngine;
/// <summary>
/// 同期呼び出しの例(直接メソッドを呼ぶ)。
/// エントリーポイントの <c>ExecuteExample</c> メソッドから <c>DoWork</c> を実行します。
/// </summary>
public class SyncCallExample : MonoBehaviour
{
/// <summary>
/// 同期で実行される処理です。
/// </summary>
public void DoWork()
{
Debug.Log("同期呼び出し:普通に呼ぶ");
}
/// <summary>
/// エントリーポイントメソッドです。
/// このメソッドは直接 <c>DoWork</c> を呼び出して処理を実行します。
/// </summary>
public void ExecuteExample()
{
DoWork();
}
}
Action、Invokeを使う
Actionでメソッドの参照を保持してInvokeを呼ぶことで、指定されたメソッドを同期的に呼び出すことができます。
actionで保持するメソッドをプログラム実行中に動的に変更することができるため、例えばゲームの状態によって実行するメソッドを変えるという使い方ができます。
using UnityEngine;
using System;
/// <summary>
/// Action デリゲートをフィールドとして保持し、
/// エントリーポイントの <c>ExecuteExample</c> メソッドで <c>PrintMessage</c> を呼び出す例です。
/// </summary>
public class ActionCallExample : MonoBehaviour
{
/// <summary>
/// Action デリゲートのフィールド。
/// <c>PrintMessage</c> を登録して利用します。
/// </summary>
private Action action;
/// <summary>
/// Action に登録される処理です。
/// </summary>
public void PrintMessage()
{
Debug.Log("同期呼び出し:Action と Invoke を使って呼ぶ");
}
/// <summary>
/// エントリーポイントメソッドです。
/// このメソッドは、Action フィールドに <c>PrintMessage</c> を登録し、Invoke() で呼び出します。
/// </summary>
public void ExecuteExample()
{
if (action == null)
{
action = PrintMessage;
//action = new Action(PrintMessage); この書き方でもよい
}
action.Invoke();
}
}
疑似非同期呼び出し
UnityではCoroutineという機能を使用して、疑似非同期的に処理を実行する事ができます。
ただしあくまでメインスレッドで動作するため重い処理を実行する場合には向きません。
ループ処理を時間分散させて実行することができるため、例えば10000のオブジェクトを作成する際に、100個ずつ処理フレームを分散して処理するなどに使います。
Coroutineの解説を調べると非同期処理と書かれていことが多いため、ここでは疑似非同期と書きますが、同一スレッドで実行されるものを非同期処理と呼ぶのも個人的には違和感があるので、非同期よりも時差処理とか分散処理の方が呼び方的にはあっている気がします。
Coroutine
疑似非同期的であるため、下記のStartCoroutineを呼び出しても、MyCoroutineの終了を待たずに次の処理が呼ばれます。ただし、最初のyieldまでは同期的実行される点には注意が必要です。
using UnityEngine;
using System.Collections;
/// <summary>
/// Coroutine を利用した疑似非同期処理の例。
/// エントリーポイントの <c>ExecuteExample</c> メソッドから <c>MyCoroutine</c> を開始します。
/// </summary>
public class CoroutineExample : MonoBehaviour
{
/// <summary>
/// コルーチン内で実行される処理です。
/// 1秒待機してから再開します。
/// </summary>
private IEnumerator MyCoroutine()
{
Debug.Log("Coroutine開始");
yield return new WaitForSeconds(1f);
Debug.Log("Coroutine再開");
}
/// <summary>
/// エントリーポイントメソッドです。
/// このメソッドは <c>MyCoroutine</c> を開始して非同期風の処理を実行します。
/// </summary>
public void ExecuteExample()
{
StartCoroutine(MyCoroutine());
}
}
UniTask
UnitTaskを使用する場合の書き方です、やっていることはCoroutineと同じです。
using UnityEngine;
using Cysharp.Threading.Tasks;
/// <summary>
/// UniTask を利用した疑似非同期処理の例。
/// ExecuteExample() で MyUniTask() を呼び出します。
/// </summary>
public class UniTaskExample : MonoBehaviour
{
/// <summary>
/// UniTask を利用した非同期処理。
/// 1000ミリ秒待機してから再開します。
/// </summary>
private async UniTask MyUniTask()
{
Debug.Log("UniTask開始");
await UniTask.Delay(1000);
Debug.Log("UniTask再開");
}
/// <summary>
/// 外部から呼び出すエントリーポイント。
/// </summary>
public void ExecuteExample()
{
MyUniTask().Forget();
}
}
非同期呼び出し(別スレッド)
こちらは別スレッドで実行される本当の意味での非同期処理です。
ただしUnity周りの処理は基本的にメインスレッドでの実行が前提になっているため、transformの操作やprefabのInstantiateなどは別スレッドで実行することはできません。
「別スレッドではSceneに存在するもの(gameObject、Component)を変更することができない」とざっくり理解をしておくと良いと思います。
では、どのように使うのかというと例えば複雑な処理を別スレッドで行って、それをメインスレッドでGameObjectに反映する。という使い方はできます。
他にもセーブデータの保存を別スレッドに任せることや、大容量のファイルを裏で読み込んでおいて、後でメインスレッドでゲームに反映させるという使い方もできるかと思います。
Threading.Task
例としてThreading.Taskを使用した例です。
このような形で定義し、ExecuteExampleAsyncをコールするとDoWorkが別スレッドで実行されます。
が、基本的にUnityは別スレッド動作をサポートしていないため、別スレッドを使用した処理を実装する場合には後述するUniTaskの使用が推奨されます。
using UnityEngine;
using System;
using System.Threading.Tasks;
/// <summary>
/// Task.Run を利用して別スレッドでの非同期処理を行う例です。
/// エントリーポイントの <c>ExecuteExample</c> メソッドから <c>DoWork</c> を実行します。
/// </summary>
public class TaskExample : MonoBehaviour
{
/// <summary>
/// 別スレッドで実行される処理です。
/// </summary>
public void DoWork()
{
Debug.Log("Task.Run:別スレッドで処理中");
}
/// <summary>
/// エントリーポイントメソッドです。
/// このメソッドは Task.Run を利用して <c>DoWork</c> を別スレッドで実行します。
/// </summary>
public async Task ExecuteExampleAsync()
{
await Task.Run(DoWork);
}
}
UniTask
UniTaskを使用して非同期でBackgroundWorkを実行する例です。
/// <summary>
/// UniTask.Run を利用して別スレッドでの非同期処理を行う例です。
/// エントリーポイントの <c>ExecuteExample</c> メソッドから <c>RunInBackground</c> を実行します。
/// </summary>
public class UniTaskThreadExample : MonoBehaviour
{
/// <summary>
/// 別スレッドで実行される処理です。
/// </summary>
public void BackgroundWork()
{
Debug.Log("UniTask.Run:別スレッドで処理中");
}
/// <summary>
/// UniTask.Run を利用して <c>BackgroundWork</c> を別スレッドで実行します。
/// </summary>
private async UniTask RunInBackground()
{
await UniTask.RunOnThreadPool(BackgroundWork);
}
/// <summary>
/// エントリーポイントメソッドです。
/// このメソッドは <c>RunInBackground</c> を実行して別スレッドでの処理を開始します。
/// </summary>
public void ExecuteExample()
{
RunInBackground().Forget();
}
}
さいごに
非同期処理、みなさんは使いこなせていますか?
私は非同期処理を考えるといつも脳みそが爆発しそうになるのですが、いつか理解できることを信じて今日も頑張ります。。。
最後までお読みいただき、ありがとうございます!
コメント