Megosztás a következőn keresztül:


Aszinkron visszatérési típusok (C#)

Az aszinkron metódusok a következő visszatérési típusokkal rendelkezhetnek:

  • Taskegy olyan aszinkron metódushoz, amely egy műveletet hajt végre, de nem ad vissza értéket.
  • Task<TResult>aszinkron metódushoz, amely egy értéket ad vissza.
  • voideseménykezelőhöz.
  • Bármely olyan típus, amely rendelkezik akadálymentes GetAwaiter módszerrel. A metódus által visszaadott objektumnak GetAwaiter implementálnia kell az interfészt System.Runtime.CompilerServices.ICriticalNotifyCompletion .
  • IAsyncEnumerable<T>egy aszinkron streamet visszaadó aszinkron metódushoz.

Az aszinkron metódusokról további információt az Aszinkron programozás aszinkron és várakozási (C#) című témakörben talál.

Számos más típus is létezik, amelyek a Windows számítási feladataira vonatkoznak:

Feladat visszaküldési típusa

Az olyan aszinkron metódusok, amelyek nem tartalmaznak utasítást return , vagy olyan utasítást return tartalmaznak, amely nem ad vissza operandusokat, általában visszatérési típussal Taskrendelkeznek. Ezek a metódusok akkor térnek vissza void , ha szinkronban futnak. Ha egy aszinkron metódushoz visszatérési típust Task használ, a hívó metódus operátorral await felfüggesztheti a hívó befejezését, amíg a hívott aszinkron metódus be nem fejeződik.

Az alábbi példában a WaitAndApologizeAsync metódus nem tartalmaz utasítást return , ezért a metódus egy Task objektumot ad vissza. A visszatérési Task lehetőség WaitAndApologizeAsync várandós. A Task típus nem tartalmaz tulajdonságot Result , mert nincs visszatérési értéke.

public static async Task DisplayCurrentInfoAsync()
{
    await WaitAndApologizeAsync();

    Console.WriteLine($"Today is {DateTime.Now:D}");
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Console.WriteLine("The current temperature is 76 degrees.");
}

static async Task WaitAndApologizeAsync()
{
    await Task.Delay(2000);

    Console.WriteLine("Sorry for the delay...\n");
}
// Example output:
//    Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.

WaitAndApologizeAsync várakozási kifejezés helyett várakozási utasítással várva vár, hasonlóan a szinkron üres visszatérési metódus hívási utasításához. A várakozási operátor alkalmazása ebben az esetben nem hoz létre értéket. Ha egy adott operandusnak a megfelelő operandusaawait, Task<TResult>a kifejezés Teredménye await a következő lesz: . Ha egy operandusnak a megfelelő operandusa await egy Task, akkor az await operandus egy utasítás.

A hívásokat WaitAndApologizeAsync elkülönítheti a várakozási operátor alkalmazásától, ahogy az az alábbi kódban is látható. Ne feledje azonban, hogy a Task tulajdonság nem rendelkezik tulajdonságokkal Result , és hogy nem jön létre érték, amikor egy várakozási operátort alkalmaz egy Task.

Az alábbi kód elválasztja a WaitAndApologizeAsync metódus meghívását attól, hogy a metódus által visszaadott feladatra várjon.

Task waitAndApologizeTask = WaitAndApologizeAsync();

string output =
    $"Today is {DateTime.Now:D}\n" +
    $"The current time is {DateTime.Now.TimeOfDay:t}\n" +
    "The current temperature is 76 degrees.\n";

await waitAndApologizeTask;
Console.WriteLine(output);

Tevékenység<TResult> visszatérési típusa

A Task<TResult> visszatérési típus egy olyan aszinkron metódushoz használatos, amely egy olyan visszatérési utasítást tartalmaz, amelyben az operandus .TResult

A következő példában a GetLeisureHoursAsync metódus egy return egész számot visszaadó utasítást tartalmaz. A metódusdeklarációnak meg kell adnia a visszatérési típust Task<int>. Az aszinkron FromResult metódus egy helyőrző egy olyan művelethez, amely egy DayOfWeek.

public static async Task ShowTodaysInfoAsync()
{
    string message =
        $"Today is {DateTime.Today:D}\n" +
        "Today's hours of leisure: " +
        $"{await GetLeisureHoursAsync()}";

    Console.WriteLine(message);
}

static async Task<int> GetLeisureHoursAsync()
{
    DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);

    int leisureHours =
        today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
        ? 16 : 5;

    return leisureHours;
}
// Example output:
//    Today is Wednesday, May 24, 2017
//    Today's hours of leisure: 5

Ha GetLeisureHoursAsync a metódus várakozási kifejezéséből ShowTodaysInfo hívjuk meg, a várakozási kifejezés lekéri a metódus által GetLeisureHours visszaadott feladatban tárolt egész számértéket (annak értékétleisureHours). A várakozási kifejezésekről további információt a await (várakozás) című témakörben talál.

Jobban megértheti, hogyan await kéri le az eredményt a Task<T> rendszer, ha elválasztja a hívást GetLeisureHoursAsync az alkalmazástól await, ahogyan az az alábbi kódban látható. A nem azonnal várt metódushívás GetLeisureHoursAsync a metódus deklarációjától elvárható módon ad vissza egy Task<int>értéket. A feladat a példában a getLeisureHoursTask változóhoz van rendelve. Mivel getLeisureHoursTask ez egy Task<TResult>, egy típusú TResulttulajdonságot Result tartalmaz. Ebben az esetben TResult egy egész számtípust jelöl. Amikor await alkalmazva getLeisureHoursTaskvan, a várakozás kifejezés kiértékeli a Result tulajdonság getLeisureHoursTasktartalmát. Az érték hozzá van rendelve a ret változóhoz.

Fontos

A Result tulajdonság egy blokkoló tulajdonság. Ha a tevékenység befejezése előtt megpróbálja elérni, az aktuálisan aktív szál le lesz tiltva, amíg a tevékenység be nem fejeződik, és az érték elérhetővé válik. A legtöbb esetben a tulajdonság közvetlen elérése helyett az értékhez await kell hozzáférnie.

Az előző példa lekérte a tulajdonság értékét, Result hogy letiltsa a főszálat, hogy a Main metódus az alkalmazás befejezése előtt ki tudja nyomtatni a message konzolra.

var getLeisureHoursTask = GetLeisureHoursAsync();

string message =
    $"Today is {DateTime.Today:D}\n" +
    "Today's hours of leisure: " +
    $"{await getLeisureHoursTask}";

Console.WriteLine(message);

Érvénytelen visszatérési típus

A visszatérési típust aszinkron void eseménykezelőkben használja, amelyek visszatérési típust void igényelnek. Az eseménykezelőkön kívül olyan metódusok esetében, amelyek nem adnak vissza értéket, inkább egy Task értéket kell visszaadnia, mert a visszaadott void aszinkron metódus nem várható meg. Az ilyen metódusok hívóinak a befejezést úgy kell folytatniuk, hogy nem kell megvárni az úgynevezett aszinkron metódus befejezését. A hívónak függetlennek kell lennie az aszinkron metódus által létrehozott értékektől vagy kivételektől.

A void-returning async metódus hívója nem tudja elkapni a metódusból származó kivételeket. Az ilyen kezeletlen kivételek valószínűleg az alkalmazás sikertelenségéhez vezetnek. Ha egy metódus kivételt Task ad vissza, vagy Task<TResult> kivételt ad vissza, a kivételt a visszaadott tevékenység tárolja. A kivétel újra meg fog jelenni, amikor a feladatra vár. Győződjön meg arról, hogy minden kivételt okozó aszinkron metódus visszatérési Task típussal rendelkezik, és Task<TResult> hogy a metódushoz érkező hívások várnak.

Az alábbi példa egy aszinkron eseménykezelő viselkedését mutatja be. A példakódban az aszinkron eseménykezelőnek tudatnia kell a főszállal, hogy mikor fejeződik be. Ezután a fő szál megvárhatja az aszinkron eseménykezelő befejezését, mielőtt kilépne a programból.

public class NaiveButton
{
    public event EventHandler? Clicked;

    public void Click()
    {
        Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
        Clicked?.Invoke(this, EventArgs.Empty);
        Console.WriteLine("All listeners are notified.");
    }
}

public class AsyncVoidExample
{
    static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();

    public static async Task MultipleEventHandlersAsync()
    {
        Task<bool> secondHandlerFinished = s_tcs.Task;

        var button = new NaiveButton();

        button.Clicked += OnButtonClicked1;
        button.Clicked += OnButtonClicked2Async;
        button.Clicked += OnButtonClicked3;

        Console.WriteLine("Before button.Click() is called...");
        button.Click();
        Console.WriteLine("After button.Click() is called...");

        await secondHandlerFinished;
    }

    private static void OnButtonClicked1(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 1 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 1 is done.");
    }

    private static async void OnButtonClicked2Async(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 2 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 2 is about to go async...");
        await Task.Delay(500);
        Console.WriteLine("   Handler 2 is done.");
        s_tcs.SetResult(true);
    }

    private static void OnButtonClicked3(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 3 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 3 is done.");
    }
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
//    Handler 1 is starting...
//    Handler 1 is done.
//    Handler 2 is starting...
//    Handler 2 is about to go async...
//    Handler 3 is starting...
//    Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
//    Handler 2 is done.

Általános aszinkron visszatérési típusok és ValueTask<TResult>

Az aszinkron metódus bármely olyan típust visszaadhat, amely akadálymentes GetAwaiter metódussal rendelkezik, amely egy váró típusú példányt ad vissza. Emellett a metódusból GetAwaiter visszaadott típusnak rendelkeznie kell az System.Runtime.CompilerServices.AsyncMethodBuilderAttribute attribútummal. A fordító által beolvasott attribútumokról szóló cikkben vagy a Feladattípus-szerkesztő minta C#-specifikációjában talál további információt.

Ez a funkció kiegészíti a várható kifejezéseket, amelyek az operandusra awaitvonatkozó követelményeket írják le. Az általános aszinkron visszatérési típusok lehetővé teszik, hogy a fordító különböző típusú metódusokat hozzon létre async . Az általános aszinkron visszatérési típusok lehetővé teszik a teljesítmény javítását a .NET-kódtárakban. Task<TResult> Mivel Task referenciatípusok, a teljesítmény szempontjából kritikus útvonalak memóriafoglalása, különösen ha a lefoglalás szoros ciklusokban történik, hátrányosan befolyásolhatja a teljesítményt. Az általánosított visszatérési típusok támogatása azt jelenti, hogy hivatkozástípus helyett egy egyszerű értéktípust adhat vissza a további memóriafoglalások elkerülése érdekében.

A .NET egy általános feladat-visszaadó érték egyszerűsített implementációjaként biztosítja a System.Threading.Tasks.ValueTask<TResult> struktúrát. Az alábbi példa a ValueTask<TResult> struktúra használatával kéri le két kockadobás értékét.

class Program
{
    static readonly Random s_rnd = new Random();

    static async Task Main() =>
        Console.WriteLine($"You rolled {await GetDiceRollAsync()}");

    static async ValueTask<int> GetDiceRollAsync()
    {
        Console.WriteLine("Shaking dice...");

        int roll1 = await RollAsync();
        int roll2 = await RollAsync();

        return roll1 + roll2;
    }

    static async ValueTask<int> RollAsync()
    {
        await Task.Delay(500);

        int diceRoll = s_rnd.Next(1, 7);
        return diceRoll;
    }
}
// Example output:
//    Shaking dice...
//    You rolled 8

Az általános aszinkron visszatérési típus írása speciális forgatókönyv, és speciális környezetekben való használatra van megcélzva. Fontolja meg inkább a Task, Task<T>és ValueTask<T> a típusok használatát, amelyek az aszinkron kód legtöbb forgatókönyvét lefedik.

A C# 10-es és újabb verzióiban az AsyncMethodBuilder attribútumot alkalmazhatja egy aszinkron metódusra (az aszinkron visszatérési típus deklarációja helyett) az adott típus szerkesztőjének felülbírálásához. Ezt az attribútumot általában a .NET-futtatókörnyezetben megadott másik szerkesztő használatára alkalmazza.

Aszinkron streamek IAsyncEnumerable<T-vel>

Az aszinkron metódus visszaadhat egy aszinkron streamet, amelyet a IAsyncEnumerable<T>következő jelöl: . Az aszinkron stream lehetővé teszi a streamből beolvasott elemek számbavételét, amikor ismétlődő aszinkron hívásokkal rendelkező adattömbökben jönnek létre elemek. Az alábbi példa egy aszinkron metódust mutat be, amely aszinkron streamet hoz létre:

static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
    string data =
        @"This is a line of text.
              Here is the second line of text.
              And there is one more for good measure.
              Wait, that was the penultimate line.";

    using var readStream = new StringReader(data);

    string? line = await readStream.ReadLineAsync();
    while (line != null)
    {
        foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
        {
            yield return word;
        }

        line = await readStream.ReadLineAsync();
    }
}

Az előző példa aszinkron módon olvas be sorokat egy sztringből. Az egyes sorok beolvasása után a kód számba adja a sztring minden egyes szavát. A hívók az utasítással számba vennék az await foreach egyes szavakat. A metódus akkor vár, amikor aszinkron módon be kell olvasnia a következő sort a forrássztringből.

Lásd még