5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

とりあえずUniTask学んでみたのでまとめてみる

Last updated at Posted at 2022-10-29

最初に

スタジオしまづというUnityのオンラインコミュニティで、勉強会をします。
その資料で、UniTaskについて軽くまとめたものになります。

目次

  • 同期処理について
  • 非同期処理について
  • コルーチンの復習
  • await / asyncについて
  • スレッド処理について
  • UniTaskの面倒なところ
  • おわりに

同期処理について

複数の処理を実行するときに、一つずつ順番に処理が実行される

@Sample01
using UnityEngine;

// ======================================================================
// 同期処理について
// ======================================================================
public class Sample01 : MonoBehaviour
{
    void Start()
    {
        A();
    }

    void A()
    {
        Debug.Log("A");
        B();
        Debug.Log("C");
    }

    void B()
    {
        Debug.Log("B");
    }
}

実行結果は A → B → C

図にするとこんな感じ
1-04.png

非同期処理について

複数の処理を実行するときに、実行結果の前に処理が戻ってくる

@Sample02
using System.Collections;
using UnityEngine;

// ======================================================================
// 非同期処理
// ======================================================================
public class Sample02 : MonoBehaviour
{
    void Start()
    {
        A();
    }

    void A()
    {
        Debug.Log("A");
        StartCoroutine(B());
        Debug.Log("C");
    }

    IEnumerator B()
    {
        yield return new WaitForSeconds(1);
        Debug.Log("B");
    }
}

実行結果は A → C → B
図にするとこんな感じ
333.png

コルーチンの復習

@Sample03
using System.Collections;
using UnityEngine;


// ======================================================================
// コルーチンの復習
// ======================================================================
public class Sample03 : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(SampleCoroutine());
    }

    IEnumerator SampleCoroutine()
    {
        Debug.Log("スタート");

        // 2秒待機する
        yield return new WaitForSeconds(2f);

        Debug.Log("ゴール");
    }
}

こんな感じで、2秒後に〇〇するみたいな処理ができます。

コルーチンにも欠点があり、

  • 関数に戻り値が持たせられない(厳密にはできる)
  • MonoBehaviourを継承していないと使えない
  • ゲームオブジェクトが破壊や非表示されると処理が止まる

などがあります。

await / asyncについて

await / acyncというのは、C#にある非同期処理の機能です。

コルーチンと違い、戻り値を持たせられるし、
MonoBehaviourがなくても使えます。
ただし、ゲームオブジェクトが破壊されても動くので、
注意が必要です。(後述します。)

@Sample04
using UnityEngine;
using Cysharp.Threading.Tasks;
using System;


// ======================================================================
// お試しUniTask
// ======================================================================
public class Sample04
{
    async void Start()
    {
        await Test();
    }

    async UniTask Test()
    {
        Debug.Log("スタート");

        // 2秒待機する
        await UniTask.Delay(TimeSpan.FromSeconds(2));

        Debug.Log("ゴール");
    }
}

コルーチンをawait / asyncに置き換えるとこんな感じです。

@Sample05

using UnityEngine;

// ======================================================================
// お試しUniTaskその2
// ======================================================================
public class Sample05 : MonoBehaviour
{
    async void Start()
    {
        Sample05_2 sample05_2 = new Sample05_2();

        // Test()がreturnするまで待機する
        var str = await sample05_2.Test();
        Debug.Log(str);
    }
}

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

public class Sample05_2
{
    public async UniTask<string> Test()
    {
        Debug.Log("スタート");

        // 2秒待機する
        await UniTask.Delay(TimeSpan.FromSeconds(2));

        return "ゴール";
    }
}

戻り値を持たせるとこんな感じです。

ちなみに、

var str = await sample05_2.Test();

awaitというのは処理が終わるまで待ってくれるという意味です。
このawaitを書かないと、"str"が空のまま出力されてしまいますね。

スレッド処理について

スレッド処理というのは、プログラムを実行するときに
複数でマルチスレッドとして処理させたり、
単数でシングルスレッドとして処理できます。

例えばゲーム制作だと、
ゲームの中の計算をする処理と
ゲームのグラフィックを描画する処理があると思います。
Unityで普通に製作していると、シングルスレッドで
この2つの処理を同時に動かしているので、
計算の処理が複雑になったりすると重くなってしまいます。

そのため、計算部分は別のスレッドにやってもらおう!
みたいな使い方ができます。

詳しくは書かないですが、C#にはTaskというスレッド処理の機能が元々あります。
Unityではそのまま使うのが大変な機能でしたが、それを使いやすくしてくれたのがUniTaskに
なります。

@Sample06
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;


// ======================================================================
// お試しスレッド処理
// ======================================================================
public class Sample06 : MonoBehaviour
{
   async void Start()
    {
        Debug.Log(Thread.CurrentThread.ManagedThreadId);
        
        // 処理するスレッドの変更
        await UniTask.SwitchToThreadPool();
        
        Debug.Log(Thread.CurrentThread.ManagedThreadId);

        // 処理するスレッドをメインスレッドに変更
        await UniTask.SwitchToMainThread();
        
        Debug.Log(Thread.CurrentThread.ManagedThreadId);
    }


}

簡単にですが、これで処理するスレッドを切り替えています。

実はメインスレッドでないと、Unityの標準のtransformとかが
使えなかったりしますが、今回は軽い紹介にします。

UniTaskの面倒なところ

最後にUniTaskの面倒なところをまとめます。
これがあるから個人開発はコルーチンのままでいいのかなーってなっちゃうくらいですね。

前述の通り、UniTaskの処理はゲームオブジェクトが破壊されても動きます。
そのため破壊されたら処理を止めたい場合、自分でその処理を書かないといけないです。

今回は左クリックしたら自身のゲームオブジェクトを破壊し、処理を
止めるというコードを書きます。

@Sample07
using UnityEngine;
using Cysharp.Threading.Tasks; // UniTaskに必要
using System.Threading; // CancelToken系に必要
using System;


// ======================================================================
// お試し中断処理
// ======================================================================
public class Sample07 : MonoBehaviour
{
    private CancellationTokenSource _cts = new CancellationTokenSource();

    async void Start()
    {
        Debug.Log("Start");

        // トークンソースからトークンを生成
        CancellationToken token = _cts.Token;

        // 非同期が止まったときの例外を書かないとエラーになる
        try
        {
            await A(token);
        }
        catch (OperationCanceledException)
        {
            Debug.Log("処理を強制的に終わらせたよ");
        }
    }

    // 5秒待機するUniTask
    async UniTask A(CancellationToken token)
    {
        await UniTask.Delay(5000, false, PlayerLoopTiming.Update, token);
        Debug.Log("待てたよ");
    }

    void Update()
    {
        // クリックしたらキャンセル
        if (Input.GetMouseButtonDown(0))
        {
            Destroy(gameObject);
        }
    }

    // gameobject破壊時にメモリの開放
    void OnDestroy()
    {
        _cts.Cancel();
        _cts.Dispose();
    }
}

僕もあまりなれていないですが、普段使わないようなDispose()でのメモリの開放や、
try-catch文での例外処理など、個人開発ではあまり使わない処理を書かないといけないです。

逆を言えば、こういうC#の機能の勉強をしたい方がUniTaskに取り組むのも良いかもしれない
ですね。

おわりに

勉強会用の資料ということで、まとめてみました。
何かしら間違っている部分があったら申し訳ないです。
その時は教えていただけると助かります!

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?