Sdílet prostřednictvím


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

  1. 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é.

  2. 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 in endMethod 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 nebo await Task.Factory.FromAsync spuštění pod plánovačem v okamžiku vytvoření úlohy, což je plánovač agregačního intervalu.

  3. 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.

  4. 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é pro List<T>.ForEach(async element => ...) a jakoukoli jinou metodu Action<T>, která přijímá , protože asynchronní delegát bude převeden na delegáta async 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.StartNewC# 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). notIntendedTasknení ú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.Runa 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 awaitkó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.Defaulthodnotou , 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.Runknihovny – 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.