ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

C# - 同期処理・非同期処理(Thread、Task)

初めに

 プログラミングには、同期処理と非同期処理という2つの処理方法があります。この記事では、同期処理と非同期処理がそれぞれどのようなものであるか、そしてC#においてそれぞれを実行する方法についてまとめます。



同期処理

同期処理とは

 同期処理とは、

複数の処理を実行する際、1つずつ上から順番に実行する処理のこと

です。いわゆる、順次処理のことで、通常の処理は同期処理だと思ってよいでしょう。

 同期処理では、次のようなソースコードでは、Method1 が終わってから Method2 が実行されます。

Method1();
Method2();
同期処理のメリット
  • ソースコードの上から順に読んでいくだけなので、どのように進んでいくか読むことが容易
  • タスクが完了するのを待つことで、結果を確実に取得することができる
同期処理のデメリット
  • 処理同士でタイミングを合わせる必要がある場合、ある処理が完了するまで別の処理は完全に停止する

非同期処理

非同期処理とは

 非同期処理とは、

複数の処理をする際に順番を待たないで次の処理が実行されます処理方法のこと

です。

 Unity においては、コルーチンは非同期処理にあたります。

 コルーチンについては、次のリンク先に記事をご覧ください。

fineworks-fine.hatenablog.com

 例えば、次のようなソースコードを実行すると、Start は上から処理されます。Test1 はコルーチンなので、Test1 の終了を待たずに Test2 が開始されます。同様に Test2 もコルーチンなので、Test2 の終了を待たずに "Start終了" のログが表示されています。

using System.Collections;
using UnityEngine;

public class Test : MonoBehaviour
{
    void Start()
    {
        Debug.Log("Start開始");
        StartCoroutine(Test1());
        StartCoroutine(Test2());
        Debug.Log("Start終了");
    }

    IEnumerator Test1()
    {
        Debug.Log("Test1開始");
        yield return null;            // 1フレーム待機
        Debug.Log("Test1終了");
    }

    IEnumerator Test2()
    {
        Debug.Log("Test2開始");
        yield return null;            // 1フレーム待機
        Debug.Log("Test2終了");
    }
}

(実行結果)


非同期処理のメリット
  • 複数の処理を並行して行えるので、パフォーマンスの向上が見込める
非同期処理のデメリット
  • 制御が複雑になる
  • 実行中のタスクを監視し、完了したタスクからの結果を収集するための追加のコードが必要なことがある

C# における非同期処理

Thread

 Thread は直訳では「糸、筋道」であり、プログラミングでは、一連の処理の流れを意味します。複数の処理を並行して行うものをマルチスレッドといいます。

 C# では Thread クラスを使うことで、非同期処理をすることができます。

 Thread は次のように使用します。

(Thread クラスの基本的な使い方)

  • Thread クラスを使うために、名前空間に using System.Threading; を記述する
  • スレッドで使いたい関数を記述する
  • Thread クラスのコンストラクタを渡し、Thread クラスを構築する
  • ThreadStart を使い、使いたい関数を指定する

 なお、非同期処理をする中で、他のスレッドを待つ必要がある場合があります。その場合、Thread クラスにある sleep 関数と Join 関数を使用します。

関数 説明
Thread.Sleep 指定したミリ秒数の間、現在のスレッドを中断する
Thread.Join このインスタンスが表すスレッドが終了するまで、呼び出し元のスレッドをブロックする

(例)

using System.Threading;  //Threadクラスを使うために追加
using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    void Start()
    {
        //Threadクラスの構築
        //ThreadStartを使い, 使いたい関数を指定する
        Thread thread = new Thread(new ThreadStart(Method1));

        //Threadの開始
        thread.Start();

       //Threadが終わるまで(今回は Method1 が終わるまで)待機
       thread.Join();

        Method2();
    }

    void Method1()
    {
        Debug.Log("Method1開始");
        Thread.Sleep(1000);    //1000ミリ秒待機
        Debug.Log("Method1終了");
    }

    void Method2()
    {
        Debug.Log("Method2開始");
        Thread.Sleep(500);  //500ミリ秒待機
        Debug.Log("Method2終了");
    }
}

(実行結果)

Thread クラスの関数は下のドキュメントをご覧ください。

learn.microsoft.com

Task

 Task クラスを使用しても非同期処理を行うことができます。

 Task クラスを使用した非同期処理は基本的には次のように行います。

(Task クラスの基本的な使い方)

  • Task クラスを使うために、名前空間に using System.Threading.Tasks; を記述する
  • 使いたい関数を記述する
  • Task.Run 関数を使い、実行する

 よく使う処理は次の通りです。

(Task クラスでよく使う関数、変数)

  • Run 関数で実行
  • Wait 関数で処理が終わるまで待機
  • Delay 関数で指定した数ミリ秒待機
  • Result で返り値を受け取れる

(例)

using System.Threading.Tasks;  //Taskクラスを使うために追加
using UnityEngine;

public class ThreadTest : MonoBehaviour
{
    void Start()
    {
        //処理したい関数に返り値がない場合
        Task task1 = Task.Run(Method1);  //Taskの実行

        task1.Wait();  //task1(今回はMethod1)が終了するまで待機

        //処理したい関数の返り値がstringの場合
        Task<string> task2 = Task.Run(Method2);  //Taskの実行

        string text = task2.Result;  //返り値の受け取り

        Debug.Log($"task2から返ってきたテキスト{text}");
    }

    void Method1()
    {
        Debug.Log("Method1開始");
        Task.Delay(1000);  //1000ミリ秒待機
        Debug.Log("Method1終了");
    }

    string Method2()
    {
        Debug.Log("Method2開始");
        Task.Delay(500);  //500ミリ秒待機
        string text = "このテキストを返す";
        return text;
    }
}

(実行結果)

Task クラスで使う関数、変数は下のドキュメントをご覧ください。
learn.microsoft.com

最後に

  • 同期処理とは、複数の処理を実行する際、1つずつ上から順番に実行する処理のこと
  • 非同期処理とは、複数の処理をする際に順番を待たないで次の処理が実行されます処理方法のこと
  • C# の非同期処理の方法として、Thread クラスを使う方法と Task クラスを使う方法がある