Async/await - Prázdná implementace asynchronní metody

Tomáš Holan       7. 1. 2013       C#, Threading       6190 zobrazení

V tomto článku si ukážeme a budeme podrobněji diskutovat jednu ze zajímavých záležitosti, na které bychom mohli narazit při používání async/await v praxi.

Mějme tento kód:

public abstract class DialogWindow : UserControl
{
    /// <summary>
    /// Akce volaná před zobrazením dialogu
    /// </summary>
    protected virtual Task Initialize() 
    {
        //TODO: v základní třídě nechceme ale provádět nic
    }

    public async void Show()
    {
        //Dáme dialogu možnost provést (asynchronní) inicializaci ještě před vlastním zobrazením dialogu
        await Initialize();

        //... 
    }
}

public class FooDialog : DialogWindow
{
    protected override async Task Initialize()
    {
        await LoadData();
        //...
    }
}

Zde třída DialogWindow představuje základní třídu pro obecný dialog. V této třídě chceme zavést metodu Initialize sloužící pro volání případných inicializačních akcí, které se mají provést ještě předtím než je vlastní dialog zobrazen (metodou Show). Pozor ale, že v implementaci metody Initialize konkrétního dialogu chceme umožnit volání i asynchronních akcí, proto tuto metodu zavedeme jako:

Task Initialize()

Jak jsme si již dříve vysvětlili tato signatura nám umožňuje v případě potřeby napsat tělo metody v odvozené třídě  jako asynchronní “workflow” pomoci klíčového slova async a v metodě Show “počkat” pomoci klíčového slova await na dokončení asynchronně spouštěných akcí.

Protože obecně může existovat dialog, který žádnou inicializaci potřebovat nebude, měla by být v základní třídě pouze nějaká “prázdná implementace”. U ní budeme zároveň chtít, aby podporovala “fast path” tj. aby se při await nepřerušil běh volající metody a pokračovalo se hned akcemi za await (metoda Show tedy proběhne v takovém případě celá synchronně).

Jak tuto “prázdnou implementaci” udělat?

Možná by někoho napadlo, zda by nebylo možné použit klíčové slovo async a prázdného těla metody tj. deklarovat metodu Initialize takto:

protected virtual async Task Initialize() { /*warning CS1998*/ }

Takový kód se sice zkompiluje, ale způsobí warning CS1998:

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Který nám sice oznamuje, že protože metoda neobsahuje klíčové slovo await, bude se celá vykonávat synchronně – což my chceme – ale také, že klíčové slovo async by jsme měli použít pouze, pokud metoda obsahuje klíčové slovo await (klíčové slovo async by tam také v podstatě ani být nemuselo).

Takže takto ne.

Korektní řešení je v metodě Initialize vytvořit a vrátit novou instanci třídy Task, libovolného ale již dokončeného (podmínka pro “fast path”) Tasku.

To lze zajistit několika způsoby, například lze použit třídu TaskCompletionSource<TResult>:

protected virtual Task Initialize()
{
    var tcs = new TaskCompletionSource<object>();
    tcs.SetResult(null);
    return tcs.Task;
}

Všimněte si, že existuje pouze generická verze třídy TaskCompletionSource<TResult>. Protože naše asynchronní akce ale nemá žádný výsledek (jen chceme umět “počkat” na její dokončení), je zde doporučováno (*) použít TaskCompletionSource<object> a hodnotu null.

To je ale jen opis toho co dělá pomocná metoda Task.FromResult<TResult> (nebo TaskEx.FromResult<TResult> v případě Async Targeting Pack pro Silverlight 5.0/.NET 4.0):

protected virtual Task Initialize() 
{ 
    return Task.FromResult(true); 
}

Opět existuje pouze generická verze metody FromResult<TResult>, zde je ale jednodušší použít FromResult<bool> s hodnotou například true, případně FromResult<int> a hodnotou 0, aby zafungovala type inference.


(*) Oficiální dokumentacek tomuto konkrétně uvádí:

There is no non-generic counterpart to TaskCompletionSource<TResult>. However, Task<TResult> derives from Task, and thus the generic TaskCompletionSource<TResult> can be used for I/O-bound methods that simply return a Task by utilizing a source with a dummy TResult (Boolean is a good default choice, and if a developer is concerned about a consumer of the Task downcasting it to a Task<TResult>, a private TResult type may be used).

Nicméně použití typu object a hodnoty null je jednodušší způsob, který nevrací žádnou informaci.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • 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