次の方法で共有


外部タスクとグレイン

設計上、グレイン コードから生成されたサブタスク (例えば、awaitContinueWithTask.Factory.StartNewを使用したもの) は、親タスクと同じアクティベーションTaskSchedulerでディスパッチされます。 そのため、グレイン コードの残りの部分と同じ シングル スレッド実行モデル を継承します。 これが、グレイン ターンベースのコンカレンシーのシングルスレッド実行の背後にあるメイン ポイントです。

場合によっては、グレイン コードで、Orleans タスク スケジュール モデルを "分割" し、別のタスク スケジューラや .NET TaskThreadPoolを明示的にポイントするなど、"特別な操作を行う" 必要がある場合があります。 たとえば、グレイン コードで同期リモート ブロッキング呼び出し (リモート I/O など) を実行する必要がある場合です。 グレイン コンテキストでブロック呼び出しを実行すると、グレインがブロックされるため、実行しないでください。 代わりに、グレイン コードは、スレッド プール スレッドでこのブロック コードを実行し、その実行の完了を結合 (await) してから、グレイン コンテキストで続行できます。 Orleans スケジューラのエスケープは、一般的な使用パターンを超えて、非常に高度でめったに必要とされることの少ない使用シナリオであると予想されます。

タスク ベースの API

  1. awaitTaskFactory.StartNew (下記参照)、 Task.ContinueWithTask.WhenAnyTask.WhenAll、および Task.Delay はすべて、現在のタスク スケジューラに従います。 つまり、別の TaskSchedulerを渡さずに既定の方法で使用すると、グレイン コンテキストで実行されます。

  2. Task.RunendMethodTaskFactory.FromAsync デリゲートは、どちらも現在のタスク スケジューラに従い "ません"。 どちらも、 TaskScheduler.Default スケジューラである .NET スレッド プール タスク スケジューラを使用します。 そのため、Task.Run内のコードと endMethodTask.Factory.FromAsync 内のは、Orleans グレインのシングル スレッド実行モデルの外部にある .NET スレッド プール上で実行されます。 ただし、await Task.Run または await Task.Factory.FromAsync 後のコードは、タスクの作成時点でアクティブだったスケジューラ、つまりグレインのスケジューラによって管理されて実行されます。

  3. Task.ConfigureAwaitfalse は、現在のタスク スケジューラをエスケープするための明示的な API です。 待ち状態の TaskTaskScheduler.Default スケジューラ (.NET スレッド プール) で実行されるため、その後のコードはこれによって実行され、グレインのシングルスレッド実行が破られます。

    注意事項

    一般に、 グレイン コードでは ConfigureAwait(false) を直接使用しないでください。

  4. シグネチャ async void を持つメソッドは、グレインと共に使用しないでください。 これらは、グラフィカル ユーザー インターフェイスのイベント ハンドラーを対象としています。 async void メソッドは、例外を処理する方法を使用せず、例外をエスケープできる場合に、現在のプロセスを直ちにクラッシュさせることができます。 これは、非同期デリゲートがList<T>.ForEach(async element => ...) デリゲートに変換されるため、Action<T> および async void を受け入れるその他のメソッドにも適用されます。

Task.Factory.StartNewasync のデリゲート

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.CurrentTask.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を使用してライブラリ呼び出しを待機している限り、グレイン コードは正しいです。 結果はまさに望んでいた通りです。ライブラリコードは、既定のスケジューラ(によって返される値)で継続を実行しますが、これは< c1 /> スレッドで継続が実行されることを保証しません。なぜなら、それらはしばしば前のスレッドでインライン化されるからです。一方、グレインコードはグレインのスケジューラで実行されます。

もう 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) グレイン コード内では使用しないでください。 ライブラリ内でのみ許可されます。