Jenis pengembalian asinkron (C#)

Metode asinkron dapat memiliki jenis pengembalian berikut:

Untuk informasi selengkapnya tentang metode asinkron, lihat Pemrograman asinkron dengan async dan await (C#).

Beberapa jenis lain juga ada yang khusus untuk beban kerja Windows:

Jenis pengembalian tugas

Metode asinkron yang tidak berisi pernyataan return atau yang berisi pernyataan returnyang tidak mengembalikan operand biasanya memiliki jenis pengembalian Task. Metode tersebut mengembalikan void jika dijalankan secara sinkron. Jika Anda menggunakan jenis pengembalian Task untuk metode asinkron, metode pemanggilan dapat menggunakan operator await untuk menangguhkan penyelesaian pemanggil hingga metode asinkron yang dipanggil selesai.

Dalam contoh berikut, metode WaitAndApologizeAsync tidak berisi pernyataan return, jadi metode mengembalikan objek Task. Mengembalikan Task memungkinkan WaitAndApologizeAsync untuk ditunggu. Jenis Task tidak menyertakan properti Result karena tidak memiliki nilai kembali.

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 ditunggu dengan menggunakan pernyataan menunggu sebagai ganti dari ekspresi menunggu, mirip dengan pernyataan panggilan untuk metode pengembalian void sinkron. Penerapan operator await dalam hal ini tidak menghasilkan nilai. Jika operand kanan await adalah Task<TResult>, ekspresi await menghasilkan hasil T. Jika operand kanan dari await adalah Task, await dan operand-nya adalah pernyataan.

Anda dapat memisahkan panggilan ke WaitAndApologizeAsync dari aplikasi operator await, seperti yang ditunjukkan kode berikut. Namun, ingat bahwa Task tidak memiliki properti Result, dan tidak ada nilai yang dihasilkan saat operator await diterapkan ke Task.

Kode berikut memisahkan pemanggilan metode WaitAndApologizeAsync dari menunggu tugas yang dikembalikan oleh metode.

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);

Jenis pengembalian Task<TResult>

Jenis pengembalian Task<TResult> digunakan untuk metode asinkron yang berisi pernyataan pengembalian dengan operand adalah TResult.

Dalam contoh berikut, metode GetLeisureHoursAsync berisi pernyataan return yang mengembalikan bilangan bulat. Deklarasi metode harus menentukan jenis pengembalian Task<int>. Metode FromResult asinkron adalah pengganti untuk operasi yang mengembalikan 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

Saat GetLeisureHoursAsync dipanggil dari dalam ekspresi await dalam metode ShowTodaysInfo, ekspresi await mengambil nilai bilangan bulat (nilai leisureHours) yang disimpan dalam tugas yang dikembalikan oleh metode GetLeisureHours. Untuk informasi selengkapnya tentang ekspresi await, lihat await.

Anda dapat lebih memahami bagaimana await mengambil hasil dari Task<T> dengan memisahkan panggilan ke GetLeisureHoursAsync dari aplikasi await, seperti yang ditunjukkan kode berikut. Panggilan ke metode GetLeisureHoursAsync yang tidak segera ditunggu mengembalikan Task<int>, seperti yang Anda harapkan dari deklarasi metode. Tugas ditetapkan ke variabel getLeisureHoursTask dalam contoh. Karena getLeisureHoursTask adalah Task<TResult>, ini berisi properti Result berjenis TResult. Dalam hal ini, TResult mewakili jenis bilangan bulat. Saat await diterapkan ke getLeisureHoursTask, ekspresi await dievaluasi ke konten properti Result dari getLeisureHoursTask. Nilai ditetapkan ke variabel ret.

Penting

Properti Result adalah properti pemblokiran. Jika Anda mencoba mengaksesnya sebelum tugasnya selesai, utas yang sedang aktif akan diblokir hingga tugas selesai dan nilainya tersedia. Dalam kebanyakan kasus, Anda harus mengakses nilai menggunakan await daripada mengakses properti secara langsung.

Contoh sebelumnya mengambil nilai properti Result untuk memblokir utas utama sehingga metode Main dapat mencetak message ke konsol sebelum aplikasi berakhir.

var getLeisureHoursTask = GetLeisureHoursAsync();

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

Console.WriteLine(message);

Jenis pengembalian void

Anda menggunakan jenis pengembalian voiddalam penanganan aktivitas asinkron, yang memerlukan jenis pengembalian void. Untuk metode selain penanganan aktivitas yang tidak mengembalikan nilai, Anda harus mengembalikan Task sebagai gantinya, karena metode asinkron yang mengembalikan void tidak dapat ditunggu. Setiap pemanggil dari metode tersebut harus terus menyelesaikan tanpa menunggu metode asinkron yang dipanggil selesai. Pemanggil harus independen dari nilai atau pengecualian apa pun yang dihasilkan oleh metode asinkron.

Pemanggil dari metode asinkron yang mengembalikan void tidak dapat menangkap pengecualian yang dimunculkan dari metode tersebut. Pengecualian yang tidak tertangani seperti itu cenderung menyebabkan aplikasi Anda gagal. Jika metode yang mengembalikan Task atau Task<TResult> memunculkan pengecualian, pengecualian disimpan dalam tugas yang dikembalikan. Pengecualian dimunculkan kembali ketika tugas ditunggu. Pastikan bahwa metode asinkron apa pun yang dapat menghasilkan pengecualian memiliki jenis pengembalian Task atau Task<TResult> dan panggilan ke metode tersebut ditunggu.

Contoh berikut menunjukkan perilaku penanganan aktivitas asinkron. Dalam kode contoh, penanganan aktivitas asinkron harus memberi tahu utas utama saat selesai. Kemudian utas utama dapat menunggu penanganan aktivitas asinkron selesai sebelum keluar dari program.

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.

Jenis pengembalian asinkron yang digeneralisasi dan ValueTask<TResult>

Metode asinkron dapat mengembalikan jenis apa pun yang memiliki metode yang dapat diakses GetAwaiter yang mengembalikan instans jenis awaiter. Selain itu, jenis yang dikembalikan dari metode GetAwaiter harus memiliki atribut System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Anda dapat mempelajari lebih lanjut dalam artikel tentang Atribut yang dibaca oleh pengkompilasi atau spesifikasi C# untuk pola penyusun Jenis tugas.

Fitur ini melengkapi ekspresi yang dapat ditunggu, yang menjelaskan persyaratan untuk operand await. Jenis pengembalian asinkron yang digeneralisasi memungkinkan kompilator menghasilkan async metode yang mengembalikan jenis berbeda. Jenis pengembalian asinkron yang digeneralisasi memungkinkan peningkatan performa di pustaka .NET. Karena Task dan Task<TResult> adalah jenis referensi, alokasi memori di jalur kritis performa, terutama ketika alokasi terjadi di perulangan ketat, dapat memengaruhi performa. Dukungan untuk jenis pengembalian umum berarti Anda dapat mengembalikan jenis nilai ringan sebagai ganti dari jenis referensi untuk menghindari alokasi memori tambahan.

.NET memberikan struktur System.Threading.Tasks.ValueTask<TResult> sebagai penerapan ringan dari nilai pengembalian tugas umum. Contoh berikut menggunakan struktur ValueTask<TResult> untuk mengambil nilai dua lemparan dadu.

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

Menulis jenis pengembalian asinkron yang digeneralisasi adalah skenario tingkat lanjut, dan ditargetkan untuk digunakan di lingkungan khusus. Pertimbangkan untuk menggunakan jenis Task, Task<T>, dan ValueTask<T> yang mencakup sebagian besar skenario untuk kode asinkron.

Di C# 10 dan yang lebih baru, Anda dapat menerapkan atribut AsyncMethodBuilder ke metode asinkron (sebagai ganti deklarasi jenis pengembalian asinkron) untuk mengambil alih penyusun untuk jenis tersebut. Biasanya Anda akan menerapkan atribut ini untuk menggunakan penyusun berbeda yang diberikan di runtime .NET.

Aliran asinkron dengan IAsyncEnumerable<T>

Metode asinkron dapat mengembalikan aliran asinkron, yang diwakili oleh IAsyncEnumerable<T>. Aliran asinkron memberikan cara untuk menghitung item yang dibaca dari aliran ketika elemen dihasilkan dalam potongan dengan panggilan asinkron berulang. Contoh berikut menunjukkan metode asinkron yang menghasilkan aliran asinkron:

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();
    }
}

Contoh sebelumnya membaca baris dari string secara asinkron. Setelah setiap baris dibaca, kode menghitung setiap kata dalam string. Pemanggil akan menghitung setiap kata menggunakan pernyataan await foreach. Metode menunggu saat perlu membaca baris berikutnya secara asinkron dari string sumber.

Lihat juga