Externe taken en korrels
Alle subtaken die zijn voortgekomen uit graancode (bijvoorbeeld door gebruik await
of Task.Factory.StartNew
ContinueWith
) 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
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.
Zowel als Task.Run de gemachtigde van TaskFactory.FromAsync de
endMethod
gedelegeerde respecteert de huidige taakplanner niet. Ze gebruiken beide deTaskScheduler.Default
scheduler, de taakplanner voor .NET-threadpools. Daarom wordt de code binnenTask.Run
en deendMethod
inTask.Factory.FromAsync
altijd uitgevoerd op de .NET-threadpool buiten het uitvoeringsmodel met één thread voor Orleans korrels. Echter, elke code na deawait Task.Run
ofawait Task.Factory.FromAsync
wordt weer uitgevoerd onder de scheduler op het moment dat de taak is gemaakt, wat de planner van het graan is.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)
.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 voorList<T>.ForEach(async element => ...)
en elke andere methode die een Action<T>, aangezien de asynchrone gemachtigde wordt gecodeerd in eenasync 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)
. notIntendedTask
is 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.Run
en 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 await
regelmaat, 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.Run
dat 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. |