前の章では、 1 つまたは複数のスレッドで同時に実行できる作業単位と呼ばれる小さなタスクを作成することで、並列プログラミングがどのように機能するかを説明しました。
この章では、まず同期コードと非同期コードの違いを紹介し、次に非同期コードが適切な場合と非同期コードを避けるべき場合について説明します。最後に、非同期コードの複雑さに対処するのに役立つ並列プログラミングの新機能について説明します。
このチュートリアルは学習プロジェクトに対応しています: Magician Dix / HandsOnParallelProgramming · GitCode
この章は段階的な要約に相当し、新しい知識はあまりなく、全体的に比較的単純です。
1. プログラム実行の種類
この本では、同期操作と非同期操作の実行順序について説明されていますが、初心者向けであり、追加の説明は必要ないと思います。同期実行とは、コードが上から下まで 1 行ずつのみ実行できることを意味します。非同期の場合は、複数のコード ブロックが同時に実行されます。同期と非同期を理解することは、それほど難しいことではありません。
ここをスキップして次のパートに進んでください。
2. 非同期プログラミングの使用に適した状況
非同期プログラミングを使用するのが適切なのはどのような場合ですか? Microsoft には次のドキュメントがあります。
次のシナリオが推奨されます。
-
操作が中間層アプリケーションから呼び出された場合。
-
ASP.NET ページで操作を呼び出す場合は、非同期ページを使用できます。
-
操作が Windows フォームや Windows Presentation Foundation (WPF) などのシングルスレッド アプリケーションから呼び出された場合。非同期イベントベースの呼び出しモデルを使用すると、結果のイベントが UI スレッドで発生し、複数のスレッドを自分で処理する必要がなく、アプリケーションの応答性が向上します。
ざっと見たところ、これは Unity の開発とは何の関係もありません。また、Unity 開発には、ネットワーク、IO、大量のデータの処理など、非同期マルチスレッドをいつ使用するかについての明確なシナリオが常にありました。つまり、パフォーマンスの観点から、タスクがメインスレッドに置かれてラグが発生する場合には、当然、非同期マルチスレッドを使用するかどうかを検討する必要があります。
3. 非同期コードを書く
非同期で実装するには次の方法があります。
-
Delegate.BeginInvoke メソッドを使用します。
-
Task クラスを使用します。
-
IAsyncResult インターフェイスを使用します。
-
async および await キーワードを使用します。
3.1. Delegate.BeginInvoke メソッドを使用する
これは委任のためのメソッドです。コードの一部を直接見てみましょう。
private void RunWithBegionInvoke()
{
Action logAction = AsyncLogAction;
logAction.BeginInvoke(null, null);
Debug.Log("RunWithBegionInvoke !!!");
}
public static async void AsyncLogAction()
{
await Task.Delay(2500);
Debug.Log("AsyncLogAction Finish !!!");
}
これを直接実行すると、結果は次のようになります。
BeginInvoke はメイン スレッドをブロックせず、非同期効果を実現していることがわかります。同時に、この関数はコールバックとパラメータもサポートしており、非常に使いやすいと言えます。この最下層は実際には IAsyncResult を使用して実装されますが、自動実装には一定の追加のオーバーヘッドがかかります。
3.2. Taskクラスを利用する
これは以前にもよく使われているので詳細は説明しませんが、第 2 章を参照してください。スキップ。
3.3. IAsyncResult インターフェースを使用する
IAsyncResult インターフェイス (システム) | Microsoft Learn非同期操作のステータスを表します。https://learn.microsoft.com/zh-cn/dotnet/api/system.iasyncresult?view=netstandard-2.1 この種のインターフェイス構文の知識は、直接コーディングすると理解しやすくなります。ここでは、独自の IAsyncResult を気軽に定義できます。もちろん、内部のパラメーターを気軽に書き留めることもできます。
/// <summary>
/// IAsyncResult 的示例代码;
/// </summary>
public class MyAsyncResult : IAsyncResult
{
public MyAsyncResult(MyAsyncState state)
{
m_AutoResetEvent = new AutoResetEvent(false);
IsCompleted = false;
m_AsyncState = state;
CompletedSynchronously = false;
}
private MyAsyncState m_AsyncState;
public object AsyncState => m_AsyncState;
private AutoResetEvent m_AutoResetEvent;
public WaitHandle AsyncWaitHandle => m_AutoResetEvent;
public bool CompletedSynchronously { get; private set; }
public bool IsCompleted { get; private set; }
}
/// <summary>
/// 自定义的数据结构
/// </summary>
public class MyAsyncState
{
public Vector3 Positon;
public Quaternion Rotation;
}
次に、彼に電話してみます。
private void RunWtihMyAsyncResult()
{
MyAsyncState state = new MyAsyncState();
state.Positon = new Vector3(50, 811, 55);
state.Rotation = Quaternion.Euler(7, 8, 9);
MyAsyncResult result = new MyAsyncResult(state);
Action logAction = AsyncLogAction;
// OnActionCallBack 的回调仍然在子线程
logAction.BeginInvoke(OnActionCallBack, result);
Debug.Log($"RunWtihMyAsyncResult : {Task.CurrentId}");
}
public static void OnActionCallBack(IAsyncResult result)
{
Debug.Log($"OnActionCallBack : {result.GetType()} | {Task.CurrentId} !");
MyAsyncResult myResult = result.AsyncState as MyAsyncResult;
MyAsyncState myState = myResult.AsyncState as MyAsyncState;
Debug.Log($"Positon : {myState.Positon}");
Debug.Log($"EulerAngles : {myState.Rotation.eulerAngles}");
Debug.Log($"IsCompleted : {result.IsCompleted}");
Debug.Log($"CompletedSynchronously : {result.CompletedSynchronously}");
Debug.Log($"OnActionCallBack : End !");
}
ご覧のとおり、ここでのコールバック MyAsyncResult は、返された IAsyncResult の AsyncState であり、入れ子人形のようなものです。システムによって返されるインターフェイス実装は System.Runtime.Remoting.Messaging.AsyncResult クラスであり、これは自動的にカプセル化される必要があります。
注: BeginInvoke によって呼び出される IAsyncResult インターフェイスはまだ子スレッド内にあります。
もちろん、上記の書き方では自分で書いた IAsyncResult はほとんど役に立たないことが分かりました...しかし、C# には一般的で簡単な書き方が用意されています。
private void RunWtihAsyncCallBack()
{
MyAsyncState state = new MyAsyncState();
state.Positon = new Vector3(888, 831, 255);
state.Rotation = Quaternion.Euler(76, 38,329);
Action logAction = TestFunction.AsyncLogAction;
logAction.BeginInvoke(TestFunction.OnActionCallBackSimple, state);
}
ここで返されるものも System.Runtime.Remoting.Messaging.AsyncResult です。予想と違うのは、コールバックがデリゲートよりも先に実行されるため、このインターフェースが何に使われるのかは今のところわかりません。
4. 非同期プログラミングの使用が適切でない状況
ここで著者は 4 つのシナリオを提供します。
-
接続プールを使用しない単一データベース内
-
コードの読みやすさと保守のしやすさにさらに注意を払う
-
シンプルな操作で稼働時間も短い
-
アプリケーションは大量の共有リソースを使用します
実際、Untiy の開発では、これらは問題ではありません。Unity は本来、すべてをメインスレッドで実行することを推奨しており、マルチスレッドは高度な使用法です。マルチスレッドを作成するプログラマは通常、マルチスレッドをいつ使用するかを知っています。Unity はサブスレッドでの多くのインターフェイスの呼び出しを無効にしているため、実際には Unity に関連するコードのほとんどはメインスレッドでのみ使用でき、使用されるサブスレッドのほとんどは IO または純粋なデータ処理です。
5. この章のセクション
この章は実際には要約であり、非同期と同期の概念を紹介し、非同期のさまざまな実装についても説明します。ただ、個人的には実践的な情報が少なく、役に立つことも前章に比べて少なく、少々水っぽいと感じています。
このチュートリアルは学習プロジェクトに対応しています: Magician Dix / HandsOnParallelProgramming · GitCode