Best practice při návrh API async/await   zodpovězená otázka

C#, VB.NET, Architektura, Threading

Hezký den všem,

mám otázku do pléna ohledně návrhu pomocí async/await.

Dejme tomu, že máme modelovou situaci a píšu knihovnu, která má poskytovat asynchronní i synchronní variantu nějaké metody. První špatný návrh je:

    public class MyClass
    {
        public async Task MyMethodAsync()
        {
            await Task.Delay(TimeSpan.FromSeconds(1));
        }

        public void MyMethod()
        {
            MyMethodAsync().Wait();
        }
    }

Pokud zavolám sychronní variantu MyMethod z prostředí, kde synchronizační kontext bude chtít požadavek po dokončení vnitřní čekací operace vrátit na hlavní vlákno. To je však blokováno metodou Wait, která nevrátí vykonávání do smyčky zpráv a tak se vytvoří deadlock.

Nejjednodušší variantou jak to vyřešit je použítí metody ConfigureAwait(false). Tou oznámím, že po dokončení operace se nemá výsledek vracet do původního synchronizačního kontextu a na dokončení async operace se "vezme" jiné vlákno:

    public async Task MyMethodAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
    }

Jenže takový postup znamená, že by se muselo ConfigureAwait zapsat všude, kde chci volat await a nezáleží mi, zda se vše vrátí do původního kontextu.

Napadají mě i další obskurdní způsoby jako vyrušení kontextu na dobu používání operace, které funguje, ale nevypadá to úplně čistě:

    public void MyMethod()
    {
        var oldContext = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(null);

        try
        {
            MyMethodAsync().Wait();
        }
        finally
        {
            SynchronizationContext.SetSynchronizationContext(oldContext);
        }
    }

Moje otázka tedy zní - jak to nejlépe řešit? Jak se nejlépe zachovat při návrhu takové knihovny?

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Ahoj,

Při návrhu public rozhraní knihovny se má dodržovat následující:

1) Neimplementovat async nad sync “async over sync” (pomoci Task.Run), pokud neumíme udělat principiálně opravdu asynchronní verzi metody.

2) Neimplementovat sync nad async “sync over async” (pomoci Wait()), protože to může vést na deadlock.

3) U každého await vždy použít ConfigureAwait(false). To má dva důvody, jednak výkonnostní, ale hlavně předejití dealocku při "chybném" použití knihovny.

viz. např. http://blogs.msdn.com/b/pfxteam/archive/...

"As a library implementer, it’s a best practice to always use ConfigureAwait(false) on all of your awaits, unless you have a specific reason not to; this is good not only to help avoid these kinds of deadlock problems, but also for performance, as it avoids unnecessary marshaling costs."

a http://blogs.msdn.com/b/pfxteam/archive/...

Celá část "“My async method never completes.”"

Takže explicitní uvádění ConfigureAwait(false) je ten správný a doporučovaný postup.

nahlásit spamnahlásit spam 3 / 3 odpovědětodpovědět

Ještě případně doporučuji toto video, kde se podobné záležitosti také řešili:

http://channel9.msdn.com/Events/Build/20...

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Zacnu trochu od lesa. Knihovna by obecne nemela mit nejake predpoklady, kde bezi (jaky ma context). Samozrejme az na vyjimky typu UI komponent atp. Pokud to tak je, je mozna neco spatne.

Z toho duvodu by kazda knihovna mela u vsech svych metod mit ConfigureAwait. Je to trochu opruz, ale nejaky "globalni switch" na to neni.

A samozrejme v API je treba davat pozor na to, ze cokoli co je do prvniho awaitu bezi vlastne synchronne. Jedine co asynchronni metoda muze udelat synchronne, je validace parametru (aby volajici dostal vyjimku hned na miste).

No a samozrejme CPU-bound operace nedela "asynchronni", protoze je to blbost. Na nich nic asynchronniho neni.

nahlásit spamnahlásit spam 2 / 2 odpovědětodpovědět

Díky za obě odpověďi,

bohužel jste mě ujistili v tom, že je správně cpát všude "ConfigureAwait", což se mi moc nelíbí :-).

Jen pro zajímavost, původní myšlenka s přehozením kontextu rozvíjí někdo například zde:

https://github.com/tejacques/AsyncBridge...

Nebo by alespoň měla existovat možnost oznámit "vše uvnitř tohoto bloku" se provede s "ConfigureAwait".

Jen mě zaráží, že .NET Framework na tohle nemá žádnou funkci. Občas je potřeba prostě vše provést hned a synchronně. Chápu, že nechtějí, aby to lidi dělali, ale někdy to jinak nejde.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback