Taskを使ったタイマー(遅延処理)で、Unityオブジェクトにアクセスする方法

2023年3月24日

Unityオブジェクトは、メインスレッドでしかアクセスができません。Taskを使ってマルチスレッド側で処理すると、Unityオブジェクトにはアクセスすることができません。

サンプルプロジェクト

プロジェクト

適当に空のゲームオブジェクトを作成して、次のメソッドをアタッチます。
コードの実行結果、また、Transform.positionが変化しているのを確認します。

Unityオブジェクトのアクセスが必要がない

Unityオブジェクトへのアクセスがないケースを参考のため掲載します。コードはシンプルになりますね。

コード

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Sample1 : MonoBehaviour
{
    void Start()
    {
        ShowLog("1.Start 開始");

        Task.Run(() => AsyncCounter());

        ShowLog("2.Start 終了 ");
    }

    async Task AsyncCounter()
    {
        ShowLog("3.AsyncCounter 開始 ");

        await Task.Delay(3000);

        ShowLog("4.AsyncCounter 終了");
    }

    int order = 0;

    void ShowLog(string msg)
    {
        Debug.Log($"順番(確認:{++order}. {msg} スレッド番号:{Thread.CurrentThread.ManagedThreadId}");
    }
}

このコードは、Unityゲームエンジンで動作するC#スクリプトのサンプルです。このスクリプトには、非同期処理を実行するAsyncCounter()メソッドが含まれています。

Sample1クラスはMonoBehaviourを継承しており、ゲームオブジェクトにアタッチされていると想定されます。Start()メソッドは、ゲームオブジェクトがアクティブになったときに自動的に呼び出されます。

Start()メソッドでは、まずShowLog()メソッドを使用して、ログに「1.Start 開始」というメッセージを出力します。その後、Task.Run()メソッドを使用して、AsyncCounter()メソッドを別のスレッドで非同期に実行します。最後に、再度ShowLog()メソッドを使用して、ログに「2.Start 終了」というメッセージを出力します。

AsyncCounter()メソッドは、asyncキーワードを使用して定義されています。asyncキーワードは、非同期処理を定義するために使用されます。このメソッドでは、まずShowLog()メソッドを使用して、「3.AsyncCounter 開始」というメッセージをログに出力します。その後、Task.Delay()メソッドを使用して、3秒間の遅延を発生させます。最後に、再度ShowLog()メソッドを使用して、「4.AsyncCounter 終了」というメッセージをログに出力します。

ShowLog()メソッドは、ログにメッセージを出力するために使用されます。このメソッドでは、順番に番号を振って、ログにメッセージを出力します。Thread.CurrentThread.ManagedThreadIdを使用して、現在のスレッドのIDを取得し、ログに出力します。

実行結果

呼び出し元で遅延させる方法

呼び出し元で、Taskの終了を待ってから実行するサンプルになります。

コード

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class SampleTaskMain1 : MonoBehaviour
{
    // xx. は、実行番号順 (例えば、1. Start 開始は、最初に実行される)
    async void Start()
    {
        ShowLog("1.Start 開始 ");

        Task task = Task.Run(() => AsyncCounter());

        ShowLog("2. Start 終了 ");

        await task;

        ShowLog("5. 移動処理");

        transform.position = new Vector3(1, 0, 0);

        ShowLog("6. Start全て 終了 ");
    }

    async Task AsyncCounter()
    {
        ShowLog("3. AsyncCounter 開始 ");

        await Task.Delay(3000);

        ShowLog("4. AsyncCounter 終了");
    }

    int order = 0;

    void ShowLog(string msg)
    {
        Debug.Log($"順番(確認:{++order}. {msg} スレッド番号:{Thread.CurrentThread.ManagedThreadId}");
    }
}

実行結果

呼び出し先で遅延させる方法

現在のスレッド(メインスレッド)で実行するように指定しています。
これにより、全てのコードがメインスレッドで実行されます。

コード

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class SampleTaskMain : MonoBehaviour
{
    SynchronizationContext context;

    // xx. は、実行番号順 (例えば、1. Start 開始は、最初に実行される)
    void Start()
    {
        context = SynchronizationContext.Current;

        ShowLog("1. Start 開始");

        Task task = Task.Run(() =>
        {
            context.Post(async _ =>
           {
               Task waitTask = AsyncCounter();

               await waitTask;

               ShowLog("6. Start全て 終了 ");
           }, null);
        });

        ShowLog("2. Start 終了 " + Thread.CurrentThread.ManagedThreadId);
    }

    async Task AsyncCounter()
    {
        ShowLog("3. AsyncCounter 開始 ");

        await Task.Delay(3000);

        ShowLog("4. 移動処理");

        transform.position = new Vector3(1, 0, 0);

        ShowLog("5. AsyncCounter 終了");
    }

    int order = 0;

    void ShowLog(string msg)
    {
        Debug.Log($"順番(確認:{++order}. {msg} スレッド番号:{Thread.CurrentThread.ManagedThreadId}");
    }
}

実行結果

参考(コルーチン)

呼び出し先で遅延させる方法

コルーチンは、メインスレッドで実行されるため、呼び出し先でUnityオブジェクトにアクセスすることができます。

コード

using System.Collections;
using System.Threading;
using UnityEngine;

public class LandaSample : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        ShowLog("1. Start 開始 ");

        StartCoroutine(A());

        ShowLog("3. Start 終了 ");
    }

    IEnumerator A()
    {
        ShowLog("2. コルーチン 開始 ");

        yield return new WaitForSeconds(3);

        ShowLog("4. 移動処理");

        transform.position = new Vector3(1, 0, 0);

        ShowLog("5. コルーチン 終了");
    }

    int order = 0;

    void ShowLog(string msg)
    {
        Debug.Log($"順番(確認:{++order}. {msg} スレッド番号:{Thread.CurrentThread.ManagedThreadId}");
    }
}

実行結果

C#,Unity

Posted by hidepon