Delen via


Externe taken en korrels

Alle subtaken die zijn voortgekomen uit graancode (bijvoorbeeld door gebruik await of Task.Factory.StartNewContinueWith ) worden standaard verzonden op dezelfde activering TaskScheduler als de bovenliggende taak en nemen daarom hetzelfde uitvoeringsmodel met één thread over als de rest van de graancode. Dit is het belangrijkste punt achter de uitvoering met één thread van op korrels gebaseerde gelijktijdigheid op basis van turnrency.

In sommige gevallen moet graancode mogelijk Orleans het taakplanningsmodel uitsplitsen en iets speciaals doen, zoals expliciet een Task andere taakplanner of .NET ThreadPoolaanwijzen. Een voorbeeld van een dergelijk geval is wanneer graancode een synchrone externe blokkeringsaanroep (zoals externe IO) moet uitvoeren. Als u die blokkeringsoproep uitvoert in de graancontext, wordt het graan geblokkeerd en mag dit dus nooit worden gedaan. In plaats daarvan kan de graancode dit stukje blokkerende code uitvoeren op de thread van de thread en join (await) de voltooiing van die uitvoering en doorgaan in de graancontext. We verwachten dat ontsnappen van de Orleans scheduler een zeer geavanceerd en zelden vereist gebruiksscenario is buiten de 'normale' gebruikspatronen.

Op taken gebaseerde API's

  1. wacht op, TaskFactory.StartNew (zie hieronder), Task.ContinueWith, Task.WhenAny, Task.WhenAllalle Task.Delay respect voor de huidige taakplanner. Dit betekent dat ze op de standaard manier worden gebruikt, zonder een andere TaskSchedulerdoor te geven, ervoor zorgen dat ze in de graancontext worden uitgevoerd.

  2. Zowel als Task.Run de gemachtigde van TaskFactory.FromAsync de endMethod gedelegeerde respecteert de huidige taakplanner niet. Ze gebruiken beide de TaskScheduler.Default scheduler, de taakplanner voor .NET-threadpools. Daarom wordt de code binnen Task.Run en de endMethod in Task.Factory.FromAsync altijd uitgevoerd op de .NET-threadpool buiten het uitvoeringsmodel met één thread voor Orleans korrels. Echter, elke code na de await Task.Run of await Task.Factory.FromAsync wordt weer uitgevoerd onder de scheduler op het moment dat de taak is gemaakt, wat de planner van het graan is.

  3. Task.ConfigureAwait met false is een expliciete API om te ontsnappen aan de huidige taakplanner. Dit zorgt ervoor dat de code na een wachtende taak wordt uitgevoerd op de TaskScheduler.Default scheduler, de .NET-threadgroep, en dus de uitvoering van één thread van het graan wordt verbroken.

    Let op

    U moet in het algemeen nooit rechtstreeks in graancode gebruiken ConfigureAwait(false) .

  4. Methoden met de handtekening async void mogen niet worden gebruikt met korrels. Ze zijn bedoeld voor grafische gebeurtenis-handlers van de gebruikersinterface. async void de methode kan het huidige proces onmiddellijk vastlopen als ze een uitzondering toestaan om te ontsnappen, zonder dat de uitzondering kan worden verwerkt. Dit geldt ook voor List<T>.ForEach(async element => ...) en elke andere methode die een Action<T>, aangezien de asynchrone gemachtigde wordt gecodeerd in een async void gemachtigde.

Task.Factory.StartNew en async gemachtigden

De gebruikelijke aanbeveling voor het plannen van taken in elk C#-programma is om te gebruiken Task.Run ten gunste van Task.Factory.StartNew. Een snelle Google-zoekopdracht over het gebruik van zal suggereren dat het gevaarlijk is en dat men altijd de voorkeur moet gevenTask.Run.Task.Factory.StartNew Maar als we het uitvoeringsmodel met één thread voor ons graan willen blijven gebruiken, moeten we het dan gebruiken, dus hoe doen we het dan correct? Het gevaar bij gebruik Task.Factory.StartNew() is dat het geen systeemeigen ondersteuning biedt voor asynchrone gemachtigden. Dit betekent dat dit waarschijnlijk een bug is: var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync). notIntendedTaskis geen taak die wordt voltooid wanneer SomeDelegateAsync dat het geval is. In plaats daarvan moet u altijd de geretourneerde taak uitpakken: var task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap().

Voorbeeld van meerdere taken en de taakplanner

Hieronder ziet u voorbeeldcode waarin het gebruik van TaskScheduler.Current, Task.Runen een speciale aangepaste scheduler wordt gedemonstreert om te ontsnappen aan Orleans de korrelcontext en hoe u terug kunt gaan.

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);
}

Voorbeeld van een aanroep van code die wordt uitgevoerd op een threadpool

Een ander scenario is een stukje graancode die het taakplanningsmodel van het graan moet opsplitsen en moet worden uitgevoerd op een threadpool (of een andere, niet-graancontext), maar nog steeds een ander graan moet aanroepen. Graanoproepen kunnen worden gedaan vanuit niet-graancontexten zonder extra ceremonie.

Hier volgt code die laat zien hoe een korreloproep kan worden gemaakt van een stukje code dat binnen een korrel wordt uitgevoerd, maar niet in de graancontext.

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);
}

Werken met bibliotheken

Sommige externe bibliotheken die door uw code worden gebruikt, kunnen intern worden gebruikt ConfigureAwait(false) . Het is een goede en juiste procedure in .NET om te gebruiken ConfigureAwait(false) bij het implementeren van bibliotheken voor algemeen gebruik. Dit is geen probleem in Orleans. Zolang de code in de korrel die de bibliotheekmethode aanroept, wacht op de aanroep van de bibliotheek met een normale awaitregelmaat, is de graancode juist. Het resultaat is precies zoals gewenst: de bibliotheekcode voert vervolgen uit op de standaardplanner (de waarde die wordt geretourneerd door TaskScheduler.Default, wat niet garandeert dat de voortzettingen op een ThreadPool thread worden uitgevoerd als vervolgen, worden vaak inlined in de vorige thread), terwijl de graancode wordt uitgevoerd op de planner van het graan.

Een andere veelgestelde vraag is of er bibliotheekoproepen moeten worden uitgevoerd, Task.Rundat wil weten of de bibliotheekcode ThreadPool expliciet moet worden offload (voor graancode).Task.Run(() => myLibrary.FooAsync()) Het antwoord is: nee. U hoeft geen code te offloaden, ThreadPool met uitzondering van het geval van bibliotheekcode die een blokkerende synchrone aanroep doet. Normaal gesproken worden in elke goed geschreven en juiste .NET asynchrone bibliotheek (methoden die worden geretourneerd Task en een naam krijgen met een Async achtervoegsel) geen blokkeringsaanroepen tot gevolg. Daarom hoeft u niets te offloaden, ThreadPool tenzij u vermoedt dat de asynchrone bibliotheek buggy is of als u bewust een synchrone blokkerende bibliotheek gebruikt.

Impasses

Aangezien korrels op één thread worden uitgevoerd, is het mogelijk om een graan te blokkeren door synchroon te blokkeren op een manier die meerdere threads nodig heeft om de blokkering op te heffen. Dit betekent dat code die een van de volgende methoden en eigenschappen aanroept, een korrel kan blokkeren als de opgegeven taken nog niet zijn voltooid op het moment dat de methode of eigenschap wordt aangeroepen:

  • Task.Wait()
  • Task.Result
  • Task.WaitAny(...)
  • Task.WaitAll(...)
  • task.GetAwaiter().GetResult()

Deze methoden moeten worden vermeden in elke service met hoge gelijktijdigheid, omdat ze kunnen leiden tot slechte prestaties en instabiliteit door het .NET ThreadPool te verhongeren door threads te blokkeren die nuttig werk kunnen uitvoeren en waarvoor het .NET ThreadPool extra threads moet injecteren, zodat ze kunnen worden voltooid. Bij het uitvoeren van graancode kunnen deze methoden, zoals hierboven vermeld, ervoor zorgen dat het graan een impasse veroorzaakt en daarom moeten ze ook worden vermeden in graancode.

Als er synchronisatie-over-asynchroon werk is dat niet kan worden vermeden, kunt u dat werk het beste naar een afzonderlijke planner verplaatsen. De eenvoudigste manier om dit te doen, is bijvoorbeeld te gebruiken await Task.Run(() => task.Wait()) . Houd er rekening mee dat het sterk wordt aanbevolen om synchronisatie-over-asynchroon werk te voorkomen, omdat, zoals hierboven vermeld, de schaalbaarheid en prestaties van uw toepassing zullen lijden.

Overzicht van het werken met taken in Orleans

Welke wilt u doen? Hoe kunt u het doen?
Voer achtergrondwerk uit op thread-poolthreads van .NET. Er zijn geen graancode- of grain-aanroepen toegestaan. Task.Run
Voer een asynchrone werkroltaak uit vanuit graancode met Orleans garanties voor gelijktijdigheid op basis van turn-based (zie hierboven). Task.Factory.StartNew(WorkerAsync).Unwrap() (Unwrap)
Voer synchrone werktaak uit vanuit graancode met Orleans garanties voor gelijktijdigheid op basis van turn-based. Task.Factory.StartNew(WorkerSync)
Time-outs voor het uitvoeren van werkitems Task.Delay + Task.WhenAny
Een asynchrone bibliotheekmethode aanroepen await de aanroep van de bibliotheek
async/await gebruiken Het normale .NET Task-Async-programmeermodel. Ondersteund en aanbevolen
ConfigureAwait(false) Gebruik geen binnenkorrelcode. Alleen toegestaan in bibliotheken.