# Awaitable の完了と継続

> Understand how asynchronous code resumes on completion of an awaited task and how this affects the function and performance of your application.

`await` 演算子は、囲む async メソッドの実行をサスペンドします。これにより、呼び出し元スレッドは待機中に他の処理を実行できます。待機中の `Task` または `Awaitable` が完了したら、非同期コードを再開し、サスペンドした位置から実行を継続する必要があります。非同期コードがどのように再開されるかは、アプリケーションの機能とパフォーマンスに影響します。

## .NET Task の継続##.net-task-continuations

待機開始時のコードの状態に関する情報は、同期コンテキストと呼ばれます。.NET プラットフォームは、このタイプの情報を取得するための [`SynchronizationContext`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext?view=net-8.0) クラスを提供します。`Task` の継続は、非同期メソッドが呼び出された同期コンテキストで実行されます。同期コンテキストが設定されていない場合は、[スレッドプール](https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool?view=net-8.0) を通じて実行されます。

ほとんどの Unity API はスレッドセーフではなく、メインスレッドからのみ呼び出すことができます。このため、Unity はデフォルトの [`SynchronizationContext`](https://docs.microsoft.com/en-us/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext) をカスタムの [`UnitySynchronizationContext`](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/UnitySynchronizationContext.cs) で上書きし、編集モードと再生モードの両方で、すべての .NET `Task` の継続が、デフォルトでメインスレッドで実行されるようにします。Unity のメインスレッドから `Task` を返すメソッドを呼び出すと、継続が `UnitySynchronizationContext` にポストされ、メインスレッドの次のフレームの `Update` 時に実行されます。バックグラウンドスレッドから呼び出した場合は、スレッドプールのスレッドで完了します。

同期コンテキストを取得することで、アプリケーションのパフォーマンスオーバーヘッドが増加し、メインスレッドの再開のために次のフレームの Update を待つことで、大きな待ち時間が発生します。これらの問題は、代わりに `Awaitable` を使用することで回避できます。

## Awaitable の継続##awaitable-continuations

特に記述がない限り、Unity API によって返されるすべての `Awaitable` インスタンスはメインスレッドで完了します。そのため、同期コンテキストを取得する必要はありません。`Awaitable` の継続は、処理が完了すると同期して実行されます。つまり、コードはすぐに再開でき、次のフレームを待つ必要はありません。

デフォルトの継続動作を [`Awaitable.BackgroundThreadAsync`](/engine/6000.0/script-reference/unity-engine/awaitable/background-thread-async.md) で明示的にオーバーライドできます。以下の例では、`Awaitable` がバックグラウンドスレッドで完了するように指定します。

```lang-csharp
private async Awaitable<float> DoHeavyComputationInBackgroundAsync()
{
    await Awaitable.BackgroundThreadAsync();
    // do some heavy math here
    return 42; // this will make the returned awaitable complete on a background thread
}

public async Awaitable Start()
{
    var computationResult = await DoHeavyComputationInBackgroundAsync();
    await SceneManager.LoadSceneAsync("my-scene"); // this will fail as SceneManager.LoadAsync only works from main thread
}
```

この場合、`Awaitable` がバックグラウンドスレッドで完了するように設定すると、問題が発生します。`Start` メソッドがバックグラウンドスレッドで再開すると、次の `SceneManager.LoadSceneAsync` の呼び出しは失敗します。これは、`SceneManager.LoadSceneAsync` がメインスレッドでのみ機能するためです。

この状況を修正するには、[`Awaitable.MainThreadAsync`](/engine/6000.0/script-reference/unity-engine/awaitable/main-thread-async.md) を使用して、`Awaitable` を返すメソッドをデフォルトでメインスレッドで完了させ、バックグラウンドスレッド処理のオプションも提供します。以下の例では、`DoHeavyComputationInBackgroundAsync` はデフォルトでメインスレッドで完了する前に、バックグラウンドスレッドでいくつかの処理を行います。`continueOnMainThread` パラメーターは、負荷の高い演算処理を連鎖させるときなど、必要に応じてバックグラウンドスレッドで完了するオプションを呼び出し元に提供します。

```lang-csharp
private async Awaitable<float> DoHeavyComputationInBackgroundAsync(bool continueOnMainThread = true)
{
    await Awaitable.BackgroundThreadAsync();
    // do some heavy math here
    float result = 42;

    // by default, switch back to main thread:
    if(continueOnMainThread){
        await Awaitable.MainThreadAsync();
    }
    return result;
}

public async Awaitable Start()
{
    var computationResult = await DoHeavyComputationInBackgroundAsync();
    await SceneManager.LoadSceneAsync("my-scene"); // this is ok!
}
```

\*\*注：\*\*Unity は、再生モード終了時にバックグラウンドで実行中のコードを自動的に停止しません。再生モード終了時にバックグラウンド処理をキャンセルするには、[`Application.exitCancellationToken`](/engine/6000.0/script-reference/unity-engine/application/exit-cancellation-token.md) を使用します。

## スレッド切り替えとパフォーマンス##thread-switching-and-performance

`await Awaitable.MainThreadAsync()` はメインスレッドから、`await Awaitable.BackgroundThreadAsync()` はバックグラウンドスレッドから呼び出すのが最も効率的です。どちらの場合も、完了直後にコードが再開されるためです。`MainThreadAsync` でバックグラウンドスレッドからメインスレッドに切り替えると、メインスレッドの次の [フレーム更新](/engine/6000.0/manual/scripting/object-oriented-development/managing-time-and-frame-rate/time-per-frame-updates.md) までコードは再開できません。

`Task` を返す API をメインスレッドから呼び出し、それが同期的に完了しない場合、継続の実行には少なくとも次の `Update` (30 fps で 33 ミリ秒) を待つ必要があります。ネットワークの待ち時間が問題になる場合は、これをメインスレッド外で実行し、カスタムロジックを使用してメインスレッドとネットワークタスクを同期することをお勧めします。

開発ビルドで、マルチスレッドコードで Unity API を使用しようとすると、Unity は以下のエラーメッセージを表示します。

```
UnityException: Internal_CreateGameObject can only be called from the main thread.

Constructors and field initializers will be executed from the loading thread when loading a scene.

Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
```

**重要**: パフォーマンス上の理由から、Unity は [非開発ビルド](/engine/6000.0/manual/unity-editor/editor-features/build-settings/build-profiles-reference.md) でマルチスレッド動作をチェックしません。また、ライブビルドではこのエラーを表示しません。Unity はこれらのコンテキストでマルチスレッドコードの実行を妨げませんが、実際に複数のスレッドを使用した場合、クラッシュやその他の予測できないエラーが発生する可能性があります。独自のマルチスレッドを使用する代わりに、Unity の [ジョブシステム](/engine/6000.0/manual/scripting/optimization/job-system.md) を使用する方が安全です。ジョブシステムは複数のスレッドを安全に使用してジョブを並列実行し、マルチスレッドのパフォーマンスメリットを実現します。詳細については、[ジョブシステムの概要](/engine/6000.0/manual/scripting/optimization/job-system/overview.md) を参照してください。

### Awaitable とジョブシステムの比較##awaitable-compared-to-the-job-system

Unity の `Awaitable` クラスは、以下のシナリオでジョブシステムよりも適しています。

* ファイルの操作やウェブリクエストの実行など、本質的に非同期的な処理を扱うときのコードを、ノンブロッキングな方法で簡略化する。
* 長時間実行されるタスク (1 フレーム超) をバックグラウンドスレッドにオフロードする。
* イテレーターベースのコルーチンを最新化する。
* 複数の種類の非同期処理 (フレームイベント、Unity イベント、サードパーティ製の非同期 API、I/O) を待機する。

ただし、計算負荷の高いアルゴリズムの並列化など、生存期間が短い処理には推奨されません。マルチコア CPU を最大限に活用してアルゴリズムを並列化するには、代わりに [ジョブシステム](/engine/6000.0/manual/scripting/optimization/job-system.md) を使用します。

## コードからの完了のトリガー##triggering-completion-from-code

[`AwaitableCompletionSource`](/engine/6000.0/script-reference/unity-engine/awaitable-completion-source.md) と [`AwaitableCompletionSource<T>`](/engine/6000.0/script-reference/unity-engine/awaitable-completion-source-1.md) を使用すると、ユーザーコードから完了を発生させる `Awaitable` インスタンスを作成できます。例えば、ユーザーインタラクションの終了を待つステートマシンを実装せずに、ユーザープロンプトを実装できます。

```lang-csharp

public class UserNamePrompt : MonoBehavior 
{
    TextField _userNameTextField;
    AwaitableCompletionSource<string> _completionSource = new AwaitableCompletionSource<string>();
    public void Start()
    {
        var rootVisual = GetComponent<UIDocument>().rootVisualElement;
        var userNameField = rootVisual.Q<TextField>("userNameField");
        rootVisual.Q<Button>("OkButton").clicked += ()=>{
            _completionSource.SetResult(userNameField.text);
        }
    }

    public Awaitable<string> WaitForUsernameAsync() => _completionSource.Awaitable;
}

...

public class HighScoreRanks : MonoBehavior 
{
    ...
    public async Awaitable ReportCurrentUserScoreAsync(int score)
    {
        _userNameOverlayGameObject.SetActive(true);
        var prompt = _userNameOverlayGameObject.GetComponent<UserNamePrompt>();
        var userName = await prompt.WaitForUsernameAsync();
        _userNameOverlayGameObject.SetActive(false);
        await SomeAPICall(userName, score);
    }
}
```

## 追加リソース##additional-resources

* [Awaitable のコード例リファレンス](/engine/6000.0/manual/scripting/optimization/async-await-support/async-awaitable-examples.md)
