Sdílet prostřednictvím


Externí úlohy a zrnka

Podle návrhu se všechny dílčí úkoly vytvářené z odstupňovaného kódu (např. pomocí await, ContinueWithnebo 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

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

  2. 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 a endMethod uvnitř Task.Factory.FromAsyncvždy běží ve fondu vláken .NET mimo model provádění s jedním vláknem pro Orleans zrní. Jakýkoli kód po await Task.Run nebo await 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.

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

  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í. Metoda async 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é pro List<T>.ForEach(async element => ...) a jakoukoli jinou metodu, která přijímá Action<T>, protože asynchronní delegát se změní na delegáta async 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.