設計上、グレイン コードから生成されたサブタスク (例えば、await
、ContinueWith
、Task.Factory.StartNew
を使用したもの) は、親タスクと同じアクティベーションTaskSchedulerでディスパッチされます。 そのため、グレイン コードの残りの部分と同じ シングル スレッド実行モデル を継承します。 これが、グレイン ターンベースのコンカレンシーのシングルスレッド実行の背後にあるメイン ポイントです。
場合によっては、グレイン コードで、Orleans タスク スケジュール モデルを "分割" し、別のタスク スケジューラや .NET Task
にThreadPoolを明示的にポイントするなど、"特別な操作を行う" 必要がある場合があります。 たとえば、グレイン コードで同期リモート ブロッキング呼び出し (リモート I/O など) を実行する必要がある場合です。 グレイン コンテキストでブロック呼び出しを実行すると、グレインがブロックされるため、実行しないでください。 代わりに、グレイン コードは、スレッド プール スレッドでこのブロック コードを実行し、その実行の完了を結合 (await
) してから、グレイン コンテキストで続行できます。
Orleans スケジューラのエスケープは、一般的な使用パターンを超えて、非常に高度でめったに必要とされることの少ない使用シナリオであると予想されます。
タスク ベースの API
await
、 TaskFactory.StartNew (下記参照)、 Task.ContinueWith、 Task.WhenAny、 Task.WhenAll、および Task.Delay はすべて、現在のタスク スケジューラに従います。 つまり、別の TaskSchedulerを渡さずに既定の方法で使用すると、グレイン コンテキストで実行されます。Task.Run と
endMethod
の TaskFactory.FromAsync デリゲートは、どちらも現在のタスク スケジューラに従い "ません"。 どちらも、TaskScheduler.Default
スケジューラである .NET スレッド プール タスク スケジューラを使用します。 そのため、Task.Run
内のコードとendMethod
Task.Factory.FromAsync
内のは、Orleans グレインのシングル スレッド実行モデルの外部にある .NET スレッド プール上で実行されます。 ただし、await Task.Run
またはawait Task.Factory.FromAsync
後のコードは、タスクの作成時点でアクティブだったスケジューラ、つまりグレインのスケジューラによって管理されて実行されます。Task.ConfigureAwait の
false
は、現在のタスク スケジューラをエスケープするための明示的な API です。 待ち状態のTask
が TaskScheduler.Default スケジューラ (.NET スレッド プール) で実行されるため、その後のコードはこれによって実行され、グレインのシングルスレッド実行が破られます。注意事項
一般に、 グレイン コードでは
ConfigureAwait(false)
を直接使用しないでください。シグネチャ
async void
を持つメソッドは、グレインと共に使用しないでください。 これらは、グラフィカル ユーザー インターフェイスのイベント ハンドラーを対象としています。async void
メソッドは、例外を処理する方法を使用せず、例外をエスケープできる場合に、現在のプロセスを直ちにクラッシュさせることができます。 これは、非同期デリゲートがList<T>.ForEach(async element => ...)
デリゲートに変換されるため、Action<T> およびasync void
を受け入れるその他のメソッドにも適用されます。
Task.Factory.StartNew
と async
のデリゲート
C# でタスクをスケジュールするための通常の推奨事項は、Task.Run
ではなくTask.Factory.StartNew
を使用することです。
Task.Factory.StartNew
をサッと検索すると、それが危険であることや常にTask.Run
を優先することが推奨されることが示唆されています。 ただし、グレインの シングル スレッド実行モデル内に留まるには、 Task.Factory.StartNew
を使用する必要があります。 では、それを正しく使用する方法は?
Task.Factory.StartNew()
の危険性は、非同期デリゲートのネイティブ サポートの欠如です。 これは、 var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync)
のようなコードがバグである可能性が高いということです。
notIntendedTask
は、が完了したときに完了するタスクSomeDelegateAsync
。 代わりに、返されたタスク () をvar task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap()
ラップ解除します。
例: 複数のタスクとタスク スケジューラ
以下は、TaskScheduler.Current
、Task.Run
、および特殊なカスタムスケジューラを使用してOrleansコンテキストを抜け出し、そこに戻る方法を示すサンプルコードです。
public async Task MyGrainMethod()
{
// Grab the grain's task scheduler
var orleansTS = TaskScheduler.Current;
await Task.Delay(10_000);
// Current task scheduler did not change, the code after await is still running
// in the same task scheduler.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
Task t1 = Task.Run(() =>
{
// This code runs on the thread pool scheduler, not on Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
Assert.AreEqual(TaskScheduler.Default, TaskScheduler.Current);
});
await t1;
// We are back to the Orleans task scheduler.
// Since await was executed in Orleans task scheduler context, we are now back
// to that context.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
// Example of using Task.Factory.StartNew with a custom scheduler to escape from
// the Orleans scheduler
Task t2 = Task.Factory.StartNew(() =>
{
// This code runs on the MyCustomSchedulerThatIWroteMyself scheduler, not on
// the Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
Assert.AreEqual(MyCustomSchedulerThatIWroteMyself, TaskScheduler.Current);
},
CancellationToken.None,
TaskCreationOptions.None,
scheduler: MyCustomSchedulerThatIWroteMyself);
await t2;
// We are back to Orleans task scheduler.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
}
例: スレッド プール スレッドで実行されているコードからグレイン呼び出しを行う
もう 1 つのシナリオには、グレインのタスク スケジュール モデルを "分割" し、スレッド プール スレッド (またはその他の非グレイン コンテキスト) で実行する必要があるが、別のグレインを呼び出す必要があるグレイン コードが含まれます。 グレイン呼び出しは、追加の儀式なしで、グレイン以外のコンテキストから行うことができます。
次のコードは、グレインコンテキストではなく、グレイン内で実行されているコードからグレイン呼び出しを行う方法を示しています。
public async Task MyGrainMethod()
{
// Grab the Orleans task scheduler
var orleansTS = TaskScheduler.Current;
var fooGrain = this.GrainFactory.GetGrain<IFooGrain>(0);
Task<int> t1 = Task.Run(async () =>
{
// This code runs on the thread pool scheduler,
// not on Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
int res = await fooGrain.MakeGrainCall();
// This code continues on the thread pool scheduler,
// not on the Orleans task scheduler
Assert.AreNotEqual(orleansTS, TaskScheduler.Current);
return res;
});
int result = await t1;
// We are back to the Orleans task scheduler.
// Since await was executed in the Orleans task scheduler context,
// we are now back to that context.
Assert.AreEqual(orleansTS, TaskScheduler.Current);
}
ライブラリを操作する
コードで使用される一部の外部ライブラリでは、内部で ConfigureAwait(false)
を使用する場合があります。
ConfigureAwait(false)
、.NET ではを使用することをお勧めします。 これは Orleansの問題ではありません。 ライブラリ メソッドを呼び出すグレイン コードが通常の await
を使用してライブラリ呼び出しを待機している限り、グレイン コードは正しいです。 結果はまさに望んでいた通りです。ライブラリコードは、既定のスケジューラ(
もう 1 つのよく寄せられる質問は、ライブラリ呼び出しを Task.Run
で実行する必要があるかどうか、つまり、ライブラリ コードを ThreadPool
に明示的にオフロードする必要があるかどうか ( await Task.Run(() => myLibrary.FooAsync())
など) です。 答えはノーです。 ライブラリ コードがブロック同期呼び出しを行う場合を除き、コードを ThreadPool
にオフロードする必要はありません。 通常、適切に記述された正しい .NET 非同期ライブラリ ( Task
を返し、 Async
サフィックスで名前を付けたメソッド) は、ブロック呼び出しを行いません。 したがって、非同期ライブラリがバグがあると疑われるか、同期ブロッキング ライブラリを意図的に使用しない限り、 ThreadPool
に何かをオフロードする必要はありません。
デッドロック
グレインはシングル スレッドで実行されるため、複数のスレッドのブロックを解除する必要がある方法で同期的にブロックすることで、グレインのデッドロックが発生する可能性があります。 つまり、指定されたタスクがメソッドまたはプロパティの呼び出し時点までに完了していない場合、次のいずれかのメソッドとプロパティを呼び出すコードがグレインをデッドロックする可能性があります。
Task.Wait()
Task.Result
Task.WaitAny(...)
Task.WaitAll(...)
task.GetAwaiter().GetResult()
これらのメソッドは、パフォーマンスの低下や不安定性につながる可能性があるため、コンカレンシーの高いサービスでは避けてください。 有用な作業を実行できるスレッドをブロックして .NET ThreadPool
を不足させ、 ThreadPool
が完了するために追加のスレッドを挿入する必要があります。 グレイン コードを実行すると、これらのメソッドによってグレインがデッドロックする可能性があるため、グレイン コードでも回避できます。
同期オーバー非同期の作業が避けられない場合は、その作業を別のスケジューラに移すのが良いでしょう。 最も簡単な方法は、たとえば await Task.Run(() => task.Wait())
を使用することです。 アプリケーションのスケーラビリティとパフォーマンスに悪影響を与えるので、 同期オーバー非同期 作業を回避することを強くお勧めします。
概要: Orleans のタスクを扱う方法
何を実行しようとしていますか? | 方法 |
---|---|
.NET スレッドプールのスレッドでバックグラウンド作業を実行する。 グレイン コードまたはグレイン呼び出しは許可されない。 | Task.Run |
Orleans ターンベースのコンカレンシーの保証があるグレイン コードから非同期 worker タスクを実行する (上記を参照)。 |
Task.Factory.StartNew(WorkerAsync).Unwrap() (Unwrap) |
Orleans ターンベースのコンカレンシーの保証があるグレイン コードから同期 worker タスクを実行する。 | Task.Factory.StartNew(WorkerSync) |
作業項目の実行に関するタイムアウト | Task.Delay + Task.WhenAny |
非同期ライブラリ メソッドを呼び出す | ライブラリ呼び出しの await |
async
/
await を使用する |
通常の .NET Task-Async プログラミング モデル。 サポートと推奨 |
ConfigureAwait(false) |
グレイン コード内では使用しないでください。 ライブラリ内でのみ許可されます。 |
.NET