Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Podle návrhu se všechny dílčí úkoly vytvářené z odstupňovaného kódu (např. pomocí await
, ContinueWith
nebo Task.Factory.StartNew
) odesílají na stejné aktivaci TaskScheduler jako nadřazený úkol. Proto dědí stejný jednovláknový model provádění jako zbytek kódu zrna. Toto je hlavní bod za prováděním souběžnosti založeného na jednotlivých vláknech.
V některých případech se může Orleans kód potřebovat vymanit z modelu plánování úkolů a „udělat něco zvláštního“, jako například explicitně odkazovat Task
na jiný plánovač úloh nebo plánovač úloh .NET ThreadPool. Příkladem je, když kód zrna potřebuje spustit synchronní vzdálené blokující volání (například vzdálené vstupně-výstupní operace). Provedením blokujícího volání v kontextu grain blokuje grain, a proto by se to nikdy nemělo dělat. Místo toho může kód zrnitosti provést tuto část blokující kód ve vlákně fondu vláken, spojit (await
) dokončení tohoto spuštění a pak pokračovat v kontextu zrnitosti. Očekává se, že uniknout plánovači Orleans bude velmi pokročilým a zřídka požadovaným scénářem použití, mimo rámec běžných vzorců použití.
Rozhraní API založená na úlohách
await
, TaskFactory.StartNew (viz níže), Task.ContinueWith, Task.WhenAny, Task.WhenAlla Task.Delay všechny respektují aktuální plánovač úkolů. To znamená, že jejich použití výchozím způsobem, aniž byste předali jiné TaskScheduler, způsobí, že se spustí v kontextu grain.Oba Task.Run i
endMethod
delegáti TaskFactory.FromAsyncnerespektují aktuální plánovač úkolů. Oba používajíTaskScheduler.Default
plánovač, plánovač úloh fondu vláken .NET. Proto kód uvnitřTask.Run
aendMethod
uvnitřTask.Factory.FromAsync
vždy běží ve fondu vláken .NET mimo model provádění s jedním vláknem pro Orleans zrní. Jakýkoli kód poawait Task.Run
neboawait Task.Factory.FromAsync
běží zpět pod aktivním plánovačem, který byl aktivní v okamžiku vytvoření úlohy, což je plánovač úloh.Task.ConfigureAwait with
false
je explicitní rozhraní API pro únik aktuálního plánovače úloh. Způsobí to, že kód po čekanéTask
se provede na plánovači TaskScheduler.Default (fondu vláken .NET), čímž se naruší provádění na jednom vláknu gránu.Upozornění
Obecně platí, že nikdy nepoužívejte
ConfigureAwait(false)
přímo v kódu grain.Metody s podpisem
async void
by neměly být použity s zrny. Jsou určeny pro grafické obslužné rutiny událostí uživatelského rozhraní. Metodaasync void
může okamžitě způsobit pád aktuálního procesu, pokud nechá výjimku uniknout, aniž by bylo možné výjimku zpracovat. To platí také proList<T>.ForEach(async element => ...)
a jakoukoli jinou metodu, která přijímá Action<T>, protože asynchronní delegát se změní na delegátaasync void
.
Task.Factory.StartNew
delegáti a async
delegáti
Obvyklé doporučení pro plánování úkolů v jazyce C# je používat Task.Run
místo Task.Factory.StartNew
. Rychlé vyhledávání Task.Factory.StartNew
na webu naznačuje , že to je nebezpečné a je vždy doporučeno upřednostňovat Task.Run
. Pokud ale chcete zůstat v modelu Task.Factory.StartNew
, musíte použít. Jak ho tedy správně používat? Nebezpečí u Task.Factory.StartNew()
spočívá v nedostatku nativní podpory pro asynchronní delegáty. To znamená, že kód jako var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync)
je pravděpodobně chyba.
notIntendedTask
není naplnění úkolu, když je SomeDelegateAsync
dokončen. Místo toho vždy rozbalte vrácený úkol: var task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap()
.
Příklad: Více úkolů a plánovač úkolů
Níže je ukázkový kód demonstrující použití TaskScheduler.Current
, Task.Run
, a speciální vlastní plánovač, který umožňuje vymanit se z kontextu zrnu Orleans a jak se k němu vrátit.
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);
}
Příklad: Volání komponenty "grain" z kódu spuštěného na vláknech z fondu vláken
Další scénář zahrnuje kód zrna, který se potřebuje vymanit z modelu plánování úloh zrna a spustit ho ve vlákně fondu vláken (nebo v jiném než zrně kontextu), ale stále je potřeba volat jiné zrno. Volání agregačního intervalu je možné provádět z jiných kontextů bez dalšího obřadu.
Následující kód ukazuje volání zrna z kódu běžícího uvnitř zrna, ale ne v kontextu zrna.
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);
}
Práce s knihovnami
Některé externí knihovny používané kódem můžou používat ConfigureAwait(false)
interně. Použití ConfigureAwait(false)
je dobrým a správným postupem v .NET při implementaci knihoven pro obecné účely. To není problém v Orleans. Pokud kód "grainu" čeká na volání knihovny standardní metodou await
, je kód grainu správný. Výsledek je přesně takový, jak bylo požadováno: kód knihovny spouští pokračování ve výchozím plánovači (hodnota vrácená TaskScheduler.Default
, která nezaručuje pokračování spuštěná na vláknu ThreadPool, protože jsou často integrovaná do předchozího vlákna), zatímco kód grány běží na plánovači grány.
Další často kladenou otázkou je to, jestli volání knihovny vyžadují provedení s Task.Run
—to znamená, jestli kód knihovny vyžaduje explicitní přenesení výpočtů na ThreadPool
(např. await Task.Run(() => myLibrary.FooAsync())
). Odpověď je ne. Přesunutí kódu na ThreadPool
není nutné, s výjimkou případů, kdy kód knihovny provádí blokující synchronní volání. Obvykle, žádná dobře napsaná asynchronní knihovna .NET (metody vracející Task
a pojmenované s příponou Async
) nevykonává blokující volání. Takže není třeba nic přesouvat na ThreadPool
, pokud není podezření, že by asynchronní knihovna měla chyby, nebo se záměrně nepoužívá synchronní blokující knihovna.
Zablokování
Vzhledem k tomu, že zrna se spouštějí jako jednovláknová, je možné synchronně zablokovat zrno způsobem, který vyžaduje více vláken k odblokování. To znamená, že kód volající některou z následujících metod a vlastností může způsobit zablokování datového objektu, pokud zadané úkoly nebyly dokončeny v době, kdy je vyvolána metoda nebo vlastnost.
Task.Wait()
Task.Result
Task.WaitAny(...)
Task.WaitAll(...)
task.GetAwaiter().GetResult()
Vyhněte se těmto metodám v jakékoli službě s vysokou souběžností, protože mohou vést k nízkému výkonu a nestabilitě. Hladoví platformu .NET ThreadPool
blokováním vláken, která by mohla provádět užitečnou práci, a vyžaduje ThreadPool
vložení dalších vláken pro dokončení. Při provádění kódu pro zrno mohou tyto metody způsobit zablokování, proto se jim vyhýbejte i v kódu pro zrno.
Pokud je určitá práce sync-over-async nevyhnutelná, je nejlepší přesunout tuto práci do samostatného plánovače. Nejjednodušší způsob je například použití await Task.Run(() => task.Wait())
. Upozorňujeme, že důrazně doporučujeme vyhnout se sync-over-async práci, protože škodí škálovatelnosti a výkonu aplikací.
Shrnutí: Práce s úkoly v aplikaci Orleans
Co se pokoušíte udělat? | Jak to udělat |
---|---|
Spusťte práci na pozadí na vláknech fondu vláken .NET. Nejsou povolena žádná volání odstupňovaného kódu ani zrnitosti. | Task.Run |
Spusťte asynchronní úlohu pracovního procesu z kódu agregace s Orleans zárukami souběžnosti na základě turn (viz výše). |
Task.Factory.StartNew(WorkerAsync).Unwrap() (Unwrap) |
Spusťte synchronní úlohu pracovního procesu z kódu agregace s Orleans zárukami souběžnosti na základě turn. | Task.Factory.StartNew(WorkerSync) |
Časové limity pro spouštění pracovních položek | Task.Delay + Task.WhenAny |
Volání asynchronní metody knihovny |
await volání knihovny |
Použití async /await |
Normální programovací model .NET Task-Async. Podporované a doporučené |
ConfigureAwait(false) |
Nepoužívejte vnitřní kód zrnitosti. Povoleno pouze v knihovnách. |