Bagikan melalui


Memproses tugas asinkron saat selesai (C#)

Dengan menggunakan Task.WhenAny, Anda dapat memulai beberapa tugas secara bersamaan dan memprosesnya satu per satu saat tugas selesai daripada memprosesnya sesuai urutan mulainya.

Contoh berikut menggunakan kueri untuk membuat kumpulan tugas. Setiap tugas mengunduh konten situs web tertentu. Dalam setiap perulangan sementara, panggilan yang ditunggu untuk WhenAny mengembalikan tugas dalam kumpulan tugas yang menyelesaikan unduhannya terlebih dahulu. Tugas tersebut dihapus dari koleksi dan diproses. Perulangan berulang sampai koleksi tidak berisi tugas lagi.

Prasyarat

Anda dapat mengikuti tutorial ini dengan menggunakan salah satu opsi berikut:

Membuat aplikasi contoh

Membuat aplikasi konsol .NET Core baru. Anda dapat membuatnya dengan menggunakan perintah konsol baru dotnet atau dari Visual Studio.

Buka file Program.cs di editor kode Anda, dan ganti kode yang ada dengan kode ini:

using System.Diagnostics;

namespace ProcessTasksAsTheyFinish;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

Tambahkan bidang

Dalam definisi kelas Program, tambahkan dua bidang berikut:

static readonly HttpClient s_client = new HttpClient
{
    MaxResponseContentBufferSize = 1_000_000
};

static readonly IEnumerable<string> s_urlList = new string[]
{
    "https://learn.microsoft.com",
    "https://learn.microsoft.com/aspnet/core",
    "https://learn.microsoft.com/azure",
    "https://learn.microsoft.com/azure/devops",
    "https://learn.microsoft.com/dotnet",
    "https://learn.microsoft.com/dynamics365",
    "https://learn.microsoft.com/education",
    "https://learn.microsoft.com/enterprise-mobility-security",
    "https://learn.microsoft.com/gaming",
    "https://learn.microsoft.com/graph",
    "https://learn.microsoft.com/microsoft-365",
    "https://learn.microsoft.com/office",
    "https://learn.microsoft.com/powershell",
    "https://learn.microsoft.com/sql",
    "https://learn.microsoft.com/surface",
    "https://learn.microsoft.com/system-center",
    "https://learn.microsoft.com/visualstudio",
    "https://learn.microsoft.com/windows",
    "https://learn.microsoft.com/maui"
};

HttpClient mengekspos kemampuan untuk mengirim permintaan HTTP dan menerima respons HTTP. s_urlList menampung semua URL yang akan diproses oleh aplikasi.

Memperbarui entry point aplikasi

Entry point utama ke dalam aplikasi konsol adalah metode Main. Ganti metode yang ada dengan berikut:

static Task Main() => SumPageSizesAsync();

Metode Main yang diperbarui sekarang dianggap sebagai Asinkron utama, yang memungkinkan entry point asinkron ke dalam executable. Ini dinyatakan sebagai panggilan ke SumPageSizesAsync.

Membuat metode ukuran halaman jumlah asinkron

Di bawah metode Main, tambahkan metode SumPageSizesAsync:

static async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

    int total = 0;
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:  {total:#,#}");
    Console.WriteLine($"Elapsed time:          {stopwatch.Elapsed}\n");
}

Perulangan while menghapus salah satu tugas di setiap perulangan. Setelah setiap tugas selesai, perulangan berakhir. Metode dimulai dengan membuat instans dan memulai Stopwatch. Kemudian menyertakan kueri yang, saat dijalankan, membuat kumpulan tugas. Setiap panggilan ke ProcessUrlAsync dalam kode berikut mengembalikan Task<TResult> di mana TResult adalah bilangan bulat:

IEnumerable<Task<int>> downloadTasksQuery =
    from url in s_urlList
    select ProcessUrlAsync(url, s_client);

Karena eksekusi yang ditangguhkan dengan LINQ, Anda memanggil Enumerable.ToList untuk memulai setiap tugas.

List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

Perulangan while melakukan langkah-langkah berikut untuk setiap tugas dalam koleksi:

  1. Menunggu panggilan ke WhenAny untuk mengidentifikasi tugas pertama dalam koleksi untuk menyelesaikan unduhannya.

    Task<int> finishedTask = await Task.WhenAny(downloadTasks);
    
  2. Menghapus tugas itu dari koleksi.

    downloadTasks.Remove(finishedTask);
    
  3. Menunggu finishedTask, yang dikembalikan oleh panggilan ke ProcessUrlAsync. Variabel finishedTask adalah Task<TResult> di mana TResult adalah bilangan bulat. Tugas sudah selesai, tetapi Anda menunggunya untuk mengambil panjang situs web yang diunduh, seperti yang ditunjukkan contoh berikut. Jika tugas gagal, await akan melemparkan pengecualian elemen turunan pertama yang disimpan di AggregateException, tidak seperti membaca Task<TResult>.Result properti, yang akan melemparkan AggregateException.

    total += await finishedTask;
    

Menambahkan metode proses

Tambahkan metode berikut ProcessUrlAsync di bawah metode SumPageSizesAsync:

static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
    byte[] content = await client.GetByteArrayAsync(url);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

Untuk URL tertentu, metode akan menggunakan client instans yang disediakan untuk mendapatkan respons sebagai byte[]. Panjang dikembalikan setelah URL dan panjang ditulis ke konsol.

Jalankan proyek beberapa kali untuk memverifikasi bahwa panjang yang diunduh tidak selalu muncul dalam urutan yang sama.

Perhatian

Anda dapat menggunakan WhenAny dalam perulangan, seperti yang dijelaskan dalam contoh, untuk menyelesaikan masalah yang melibatkan sejumlah kecil tugas. Namun, pendekatan lain lebih efisien jika Anda memiliki sejumlah besar tugas untuk diproses. Untuk informasi dan contoh selengkapnya, lihat Memproses tugas saat tugas selesai.

Contoh lengkap

Kode berikut adalah teks lengkap dari file Program.cs sebagai contoh.

using System.Diagnostics;

HttpClient s_client = new()
{
    MaxResponseContentBufferSize = 1_000_000
};

IEnumerable<string> s_urlList = new string[]
{
    "https://learn.microsoft.com",
    "https://learn.microsoft.com/aspnet/core",
    "https://learn.microsoft.com/azure",
    "https://learn.microsoft.com/azure/devops",
    "https://learn.microsoft.com/dotnet",
    "https://learn.microsoft.com/dynamics365",
    "https://learn.microsoft.com/education",
    "https://learn.microsoft.com/enterprise-mobility-security",
    "https://learn.microsoft.com/gaming",
    "https://learn.microsoft.com/graph",
    "https://learn.microsoft.com/microsoft-365",
    "https://learn.microsoft.com/office",
    "https://learn.microsoft.com/powershell",
    "https://learn.microsoft.com/sql",
    "https://learn.microsoft.com/surface",
    "https://learn.microsoft.com/system-center",
    "https://learn.microsoft.com/visualstudio",
    "https://learn.microsoft.com/windows",
    "https://learn.microsoft.com/maui"
};

await SumPageSizesAsync();

async Task SumPageSizesAsync()
{
    var stopwatch = Stopwatch.StartNew();

    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();

    int total = 0;
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

    stopwatch.Stop();

    Console.WriteLine($"\nTotal bytes returned:    {total:#,#}");
    Console.WriteLine($"Elapsed time:              {stopwatch.Elapsed}\n");
}

static async Task<int> ProcessUrlAsync(string url, HttpClient client)
{
    byte[] content = await client.GetByteArrayAsync(url);
    Console.WriteLine($"{url,-60} {content.Length,10:#,#}");

    return content.Length;
}

// Example output:
// https://learn.microsoft.com                                      132,517
// https://learn.microsoft.com/powershell                            57,375
// https://learn.microsoft.com/gaming                                33,549
// https://learn.microsoft.com/aspnet/core                           88,714
// https://learn.microsoft.com/surface                               39,840
// https://learn.microsoft.com/enterprise-mobility-security          30,903
// https://learn.microsoft.com/microsoft-365                         67,867
// https://learn.microsoft.com/windows                               26,816
// https://learn.microsoft.com/maui                               57,958
// https://learn.microsoft.com/dotnet                                78,706
// https://learn.microsoft.com/graph                                 48,277
// https://learn.microsoft.com/dynamics365                           49,042
// https://learn.microsoft.com/office                                67,867
// https://learn.microsoft.com/system-center                         42,887
// https://learn.microsoft.com/education                             38,636
// https://learn.microsoft.com/azure                                421,663
// https://learn.microsoft.com/visualstudio                          30,925
// https://learn.microsoft.com/sql                                   54,608
// https://learn.microsoft.com/azure/devops                          86,034

// Total bytes returned:    1,454,184
// Elapsed time:            00:00:01.1290403

Lihat juga