Externí úlohy a zrnka
Podle návrhu se všechny dílčí úlohy vytvářené z odstupňovaného kódu (například pomocí await
nebo ContinueWith
Task.Factory.StartNew
) odešlou na stejnou aktivaci TaskScheduler jako nadřazený úkol, a proto zdědí stejný model provádění s jedním vláknem jako zbytek kódu odstupňovaného kódu. 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 může být nutné rozdělit Orleans kód plánování úkolů a "udělat něco zvláštního", například explicitně odkazovat Task
na jiný plánovač úloh nebo .NET ThreadPool. Příkladem takového případu je, že kód odstupňovaného intervalu musí spustit synchronní vzdálené blokující volání (například vzdálené vstupně-výstupní operace). Spuštěním toho, že blokující volání v kontextu agregace se zablokuje, a proto by se nikdy nemělo provádět. Místo toho může kód zrnitosti provést tuto část blokující kód ve vlákně fondu vláken a spojit (await
) dokončení tohoto spuštění a pokračovat v kontextu zrnitosti. Očekáváme, že únik z Orleans plánovače bude velmi pokročilým a zřídka požadovaným scénářem použití nad rámec "normálních" vzorů použití.
Rozhraní API založená na úlohách
await, TaskFactory.StartNew (viz níže), Task.ContinueWith, Task.WhenAny, Task.WhenAll, Task.Delay všechny respektují aktuální plánovač úkolů. To znamená, že jejich použití výchozím způsobem, bez předání jiné TaskScheduler, způsobí, že se spustí v kontextu odstupňované.
Oba Task.Run i
endMethod
delegáti TaskFactory.FromAsync nerespektují aktuální plánovač úkolů. Oba používajíTaskScheduler.Default
plánovač, což je plánovač úloh fondu vláken .NET. Proto kód uvnitřTask.Run
a inendMethod
Task.Factory.FromAsync
bude vždy spuštěn ve fondu vláken .NET mimo model provádění s jedním vláknem pro Orleans zrní. Jakýkoli kód po spuštěníawait Task.Run
neboawait Task.Factory.FromAsync
spuštění pod plánovačem v okamžiku vytvoření úlohy, což je plánovač agregačního intervalu.Task.ConfigureAwait with
false
je explicitní rozhraní API pro únik aktuálního plánovače úloh. Způsobí spuštění kódu po očekávané úloze v TaskScheduler.Default plánovači, což je fond vláken .NET, a tím přeruší provádění s jedním vláknem agregace.Upozornění
Obecně byste nikdy neměli používat
ConfigureAwait(false)
přímo v odstupňovaném kódu.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í.async void
metoda může okamžitě s chybou aktuálního procesu, pokud umožňují výjimku utéct, bez způsobu zpracování výjimky. To platí také proList<T>.ForEach(async element => ...)
a jakoukoli jinou metodu Action<T>, která přijímá , protože asynchronní delegát bude převeden na delegátaasync void
.
Task.Factory.StartNew
delegáti a async
delegáti
Obvyklým doporučením pro plánování úkolů v libovolném programu jazyka Task.Factory.StartNew
C# je použití Task.Run
ve prospěch . Rychlé vyhledávání google na použití bude navrhovat, že je nebezpečné a že jeden by měl vždy upřednostňovat Task.Run
.Task.Factory.StartNew
Ale pokud chceme zůstat v modelu provádění s jedním vláknem v zrnitosti, musíme ho použít, takže jak to uděláme správně? Nebezpečí při použití Task.Factory.StartNew()
spočívá v tom, že nativně nepodporuje asynchronní delegáty. To znamená, že se jedná o pravděpodobně chybu: var notIntendedTask = Task.Factory.StartNew(SomeDelegateAsync)
. notIntendedTask
není úkol, který se dokončí, pokud SomeDelegateAsync
ano. Místo toho byste měli vždy rozbalit vrácený úkol: var task = Task.Factory.StartNew(SomeDelegateAsync).Unwrap()
.
Příklad několika úkolů a plánovače úkolů
Níže je ukázkový kód, který demonstruje použití TaskScheduler.Current
, Task.Run
a speciální vlastní plánovač pro únik z Orleans kontextu zrn 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í odstupňovaného volání z kódu, který běží ve fondu vláken
Dalším scénářem je kód odstupňovaného intervalu, který musí "rozdělit" model plánování úloh agregace a spustit ho ve fondu vláken (nebo v jiném kontextu bez agregace), ale přesto musí volat další agregační interval. Volání agregačního intervalu je možné provádět z jiných kontextů bez dalšího obřadu.
Následuje kód, který ukazuje, jak lze volání zrnit z kusu kódu, který běží uvnitř zrnka, ale ne v kontextu zrnitosti.
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, které váš kód používá, můžou používat ConfigureAwait(false)
interně. Je vhodné a správné postupy v .NET použít ConfigureAwait(false)
při implementaci knihoven pro obecné účely. To není problém v Orleans. Pokud kód v agregačním intervalu, který vyvolá metodu knihovny, čeká na volání knihovny s normálním await
kódem, je správný. Výsledek bude přesně tak, jak potřebujete – kód knihovny spustí pokračování ve výchozím plánovači (hodnota vrácená TaskScheduler.Default
hodnotou , která nezaručuje, že pokračování se budou spouštět ve vlákně ThreadPool jako pokračování, jsou často vložena v předchozím vlákně), zatímco kód grainu se spustí v plánovači agregace.
Další často kladenou otázkou je, jestli je potřeba spouštět volání Task.Run
knihovny – to znamená, jestli je potřeba explicitně přesměrovat kód knihovny na ThreadPool
(aby se dal provést Task.Run(() => myLibrary.FooAsync())
odstupňovaný kód). Odpověď je ne. Není nutné přesměrovat žádný kód s ThreadPool
výjimkou případu kódu knihovny, který provádí blokující synchronní volání. Všechny správně napsané a správné asynchronní knihovny .NET (metody, které vracejí Task
a jsou pojmenované příponou Async
), nevolají blokující volání. Proto není nutné nic přesměrovat, pokud ThreadPool
nemáte podezření, že asynchronní knihovna je chybná nebo pokud záměrně používáte synchronní blokující knihovnu.
Zablokování
Vzhledem k tomu, že se zrna spouští v jednovláknovém módě, je možné zablokovat zrno synchronním blokováním způsobem, který by k odblokování vyžadoval více vláken. To znamená, že kód, který volá některou z následujících metod a vlastností, může zablokovat agregační interval, pokud zadané úlohy ještě nebyly dokončeny v době, kdy je vyvolána metoda nebo vlastnost:
Task.Wait()
Task.Result
Task.WaitAny(...)
Task.WaitAll(...)
task.GetAwaiter().GetResult()
Tyto metody by se měly vyhnout jakékoli službě s vysokou souběžností, protože můžou vést k nízkému výkonu a nestabilitě ThreadPool
tím, že blokují vlákna, která by mohla provádět užitečnou práci a vyžaduje, aby rozhraní .NET ThreadPool
vkládal další vlákna, aby bylo možné je dokončit. Při provádění odstupňovaného kódu mohou tyto metody, jak je uvedeno výše, způsobit vzájemné zablokování, a proto by se měly také vyhnout v odstupňovaném kódu.
Pokud existuje nějaká asynchronní práce synchronizace , která se nedá vyhnout, je nejlepší přesunout tuto práci do samostatného plánovače. Nejjednodušší způsob, jak to udělat, je například použít await Task.Run(() => task.Wait())
. Upozorňujeme, že důrazně doporučujeme vyhnout se asynchronní práci, protože, jak je uvedeno výše, způsobí, že dojde k omezení škálovatelnosti a výkonu vaší aplikace.
Shrnutí práce s úkoly v 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. |