Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Jika kode Anda menerapkan skenario terikat I/O untuk mendukung permintaan data jaringan, akses database, atau pembacaan/penulisan sistem file, pemrograman asinkron adalah pendekatan terbaik. Anda juga dapat menulis kode asinkron untuk skenario terikat CPU seperti perhitungan mahal.
C# memiliki model pemrograman asinkron tingkat bahasa yang memungkinkan Anda untuk dengan mudah menulis kode asinkron tanpa harus mengelola callback atau mengikuti pustaka yang mendukung pemrograman asinkron. Model ini mengikuti apa yang dikenal sebagai pola asinkron berbasis Tugas (TAP) .
Menjelajahi model pemrograman asinkron
Objek Task dan Task<T> mewakili inti pemrograman asinkron. Objek ini digunakan untuk memodelkan operasi asinkron dengan mendukung kata kunci async dan await. Dalam kebanyakan kasus, model ini cukup sederhana untuk skenario terikat I/O dan terikat CPU. Di dalam metode async:
-
kode terikat I/O memulai operasi yang diwakili oleh objek
TaskatauTask<T>dalam metodeasync. - kode terikat CPU memulai operasi pada utas latar belakang dengan metode Task.Run.
Dalam kedua kasus, Task aktif mewakili operasi asinkron yang mungkin tidak selesai.
Kata kunci await adalah tempat keajaiban terjadi. Ini memberikan kendali kepada pemanggil fungsi yang berisi ekspresi await, dan pada akhirnya memungkinkan UI untuk responsif atau memungkinkan layanan menjadi elastis. Meskipun ada cara untuk mendekati kode asinkron selain dengan menggunakan ekspresi async dan await, artikel ini berfokus pada konstruksi tingkat bahasa.
Catatan
Beberapa contoh yang disajikan dalam artikel ini menggunakan kelas System.Net.Http.HttpClient untuk mengunduh data dari layanan web. Dalam kode contoh, objek s_httpClient adalah bidang statis dari jenis Program kelas:
private static readonly HttpClient s_httpClient = new();
Untuk informasi selengkapnya, lihat kode contoh lengkap di akhir artikel ini.
Meninjau konsep yang mendasar
Saat Anda menerapkan pemrograman asinkron dalam kode C#Anda, pengompilasi mengubah program Anda menjadi mesin status. Konstruksi ini melacak berbagai operasi dan status dalam kode Anda, seperti menghasilkan eksekusi ketika kode mencapai ekspresi await, dan memulai kembali eksekusi ketika pekerjaan latar belakang selesai.
Dalam hal teori ilmu komputer, pemrograman asinkron adalah implementasi dari model Promise asinkron.
Dalam model pemrograman asinkron, ada beberapa konsep utama untuk dipahami:
- Anda dapat menggunakan kode asinkron untuk kode terikat I/O dan terikat CPU, tetapi implementasinya berbeda.
- Kode asinkron menggunakan objek
Task<T>danTasksebagai konstruksi untuk memodelkan pekerjaan yang berjalan di latar belakang. - Kata kunci
asyncmendeklarasikan metode sebagai metode asinkron, yang memungkinkan Anda menggunakan kata kunciawaitdalam isi metode. - Saat Anda menerapkan kata kunci
await, kode menangguhkan metode yang memanggil dan mengembalikan kontrol ke pemanggilnya hingga tugas selesai. - Anda hanya dapat menggunakan ekspresi
awaitdalam metode asinkron.
Contoh terikat I/O: Mengunduh data dari layanan web
Dalam contoh ini, saat pengguna memilih tombol, aplikasi mengunduh data dari layanan web. Anda tidak ingin memblokir antarmuka pengguna (UI) pada aplikasi selama proses pengunduhan. Kode berikut menyelesaikan tugas ini:
s_downloadButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await s_httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
Kode mengekspresikan niat (mengunduh data secara asinkron) tanpa terjebak dalam interaksi dengan objek Task.
Contoh terikat CPU: Jalankan perhitungan permainan
Dalam contoh berikutnya, game seluler memberikan kerusakan pada beberapa agen di layar sebagai respons terhadap tekanan tombol. Melakukan perhitungan kerusakan bisa mahal. Menjalankan perhitungan pada utas UI dapat menyebabkan masalah interaksi tampilan dan antarmuka pengguna selama penghitungan.
Cara terbaik untuk mengerjakan tugas ini adalah dengan memulai utas latar belakang untuk menyelesaikan pekerjaan menggunakan metode Task.Run. Operasi menghasilkan hasil dengan ekspresi await. Operasi dilanjutkan ketika tugas selesai. Pendekatan ini memungkinkan UI berjalan dengan lancar saat pekerjaan selesai di latar belakang.
static DamageResult CalculateDamageDone()
{
return new DamageResult()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
};
}
s_calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
Kode dengan jelas mengekspresikan tujuan peristiwa tombol Clicked. Ini tidak memerlukan pengelolaan thread latar belakang secara manual, dan menyelesaikan tugas tanpa memblokir.
Mengenali skenario terikat CPU dan terikat I/O
Contoh sebelumnya menunjukkan cara menggunakan pengubah async dan ekspresi await untuk pekerjaan terikat I/O dan terikat CPU. Contoh untuk setiap skenario menunjukkan bagaimana kode berbeda tergantung pada tempat operasi tersebut terkait. Untuk mempersiapkan implementasi Anda, Anda perlu memahami cara mengidentifikasi kapan operasi terikat I/O atau terikat CPU. Pilihan implementasi Anda dapat sangat memengaruhi performa kode Anda dan berpotensi menyebabkan penyalahgunaan konstruksi.
Ada dua pertanyaan utama yang harus ditangani sebelum Anda menulis kode apa pun:
| Pertanyaan | Skenario | Pelaksanaan |
|---|---|---|
| Haruskah kode menunggu hasil atau tindakan, seperti data dari database? | tergantung I/O | Gunakan pengubah async dan ekspresi awaittanpa metode Task.Run. Hindari Penggunaan Pustaka Paralel Tugas. |
| Haruskah kode menjalankan komputasi yang mahal? | terikat CPU | Gunakan pengubah async dan ekspresi await, tetapi mulai pekerjaan di utas lain dengan metode Task.Run. Pendekatan ini mengatasi kekhawatiran dengan respons CPU. Jika pekerjaan sesuai untuk konkurensi dan paralelisme, pertimbangkan juga untuk menggunakan Task Parallel Library. |
Selalu ukur eksekusi kode Anda. Anda mungkin menemukan bahwa pekerjaan terikat CPU Anda tidak sebesar biaya pergantian konteks saat multithreading. Setiap pilihan memiliki kompromi. Pilih kompromi yang tepat untuk situasi Anda.
Jelajahi contoh lain
Contoh di bagian ini menunjukkan beberapa cara Anda dapat menulis kode asinkron di C#. Mereka mencakup beberapa skenario yang mungkin Anda temui.
Mengekstrak data dari jaringan
Kode berikut mengunduh HTML dari URL tertentu dan menghitung berapa kali string ".NET" terjadi dalam HTML. Kode ini menggunakan ASP.NET untuk menentukan metode pengontrol API Web, yang melakukan tugas dan mengembalikan hitungan.
Catatan
Jika Anda berencana melakukan penguraian HTML dalam kode produksi, jangan gunakan ekspresi reguler. Gunakan pustaka penguraian sebagai gantinya.
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCountAsync(string URL)
{
// Suspends GetDotNetCountAsync() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await s_httpClient.GetStringAsync(URL);
return Regex.Matches(html, @"\.NET").Count;
}
Anda dapat menulis kode serupa untuk Aplikasi Windows Universal dan melakukan tugas penghitungan setelah tombol ditekan:
private readonly HttpClient _httpClient = new HttpClient();
private async void OnSeeTheDotNetsButtonClick(object sender, RoutedEventArgs e)
{
// Capture the task handle here so we can await the background task later.
var getDotNetFoundationHtmlTask = _httpClient.GetStringAsync("https://dotnetfoundation.org");
// Any other work on the UI thread can be done here, such as enabling a Progress Bar.
// It's important to do the extra work here before the "await" call,
// so the user sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;
// The await operator suspends OnSeeTheDotNetsButtonClick(), returning control to its caller.
// This action is what allows the app to be responsive and not block the UI thread.
var html = await getDotNetFoundationHtmlTask;
int count = Regex.Matches(html, @"\.NET").Count;
DotNetCountLabel.Text = $"Number of .NETs on dotnetfoundation.org: {count}";
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
Tunggu hingga beberapa tugas selesai
Dalam beberapa skenario, kode perlu mengambil beberapa bagian data secara bersamaan. API Task menyediakan metode yang memungkinkan Anda menulis kode asinkron yang melakukan penantian nonblocking pada beberapa pekerjaan latar belakang:
- metode Task.WhenAll
- metode Task.WhenAny
Contoh berikut menunjukkan bagaimana Anda dapat mengambil data objek User untuk sekumpulan objek userId.
private static async Task<User> GetUserAsync(int userId)
{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
return await Task.FromResult(new User() { id = userId });
}
private static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
{
getUserTasks.Add(GetUserAsync(userId));
}
return await Task.WhenAll(getUserTasks);
}
Anda dapat menulis kode ini dengan lebih tepat dengan menggunakan LINQ:
private static async Task<User[]> GetUsersByLINQAsync(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
}
Meskipun Anda menulis lebih sedikit kode dengan menggunakan LINQ, berhati-hatilah saat mencampur LINQ dengan kode asinkron. LINQ menggunakan eksekusi yang ditangguhkan (atau malas), yang berarti bahwa tanpa evaluasi segera, panggilan asinkron tidak terjadi sampai urutan dijumlahkan.
Contoh sebelumnya benar dan aman, karena menggunakan Enumerable.ToArray metode untuk segera mengevaluasi kueri LINQ dan menyimpan tugas dalam array. Pendekatan ini memastikan id => GetUserAsync(id) panggilan segera dijalankan dan semua tugas dimulai secara bersamaan, sama seperti pendekatan perulangan foreach . Selalu gunakan Enumerable.ToArray atau Enumerable.ToList saat membuat tugas dengan LINQ untuk memastikan eksekusi segera dan eksekusi tugas bersamaan. Berikut adalah contoh yang menunjukkan penggunaan ToList() dengan Task.WhenAny untuk memproses tugas saat tugas selesai:
private static async Task ProcessTasksAsTheyCompleteAsync(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToList();
while (getUserTasks.Count > 0)
{
Task<User> completedTask = await Task.WhenAny(getUserTasks);
getUserTasks.Remove(completedTask);
User user = await completedTask;
Console.WriteLine($"Processed user {user.id}");
}
}
Dalam contoh ini, ToList() membuat daftar yang mendukung Remove() operasi, memungkinkan Anda menghapus tugas yang diselesaikan secara dinamis. Pola ini sangat berguna ketika Anda ingin menangani hasil segera setelah tersedia, daripada menunggu semua tugas selesai.
Meskipun Anda menulis lebih sedikit kode dengan menggunakan LINQ, berhati-hatilah saat mencampur LINQ dengan kode asinkron. LINQ menggunakan eksekusi tertunda (atau eksekusi lambat). Panggilan asinkron tidak segera terjadi seperti dalam perulangan foreach, kecuali Anda memaksakan urutan yang dihasilkan untuk beriterasi dengan metode .ToList() atau .ToArray().
Anda dapat memilih antara Enumerable.ToArray dan Enumerable.ToList berdasarkan skenario Anda:
- Gunakan
ToArray()saat Anda berencana untuk memproses semua tugas bersama-sama, seperti denganTask.WhenAll. Array efisien untuk skenario di mana ukuran kumpulan tetap. - Gunakan
ToList()saat Anda perlu mengelola tugas secara dinamis, seperti denganTask.WhenAnytempat Anda dapat menghapus tugas yang selesai dari koleksi setelah selesai.
Meninjau pertimbangan untuk pemrograman asinkron
Dengan pemrograman asinkron, ada beberapa detail yang perlu diingat yang dapat mencegah perilaku tak terduga.
Gunakan await di dalam isi metode async()
Saat Anda menggunakan pengubah async, Anda harus menyertakan satu atau beberapa ekspresi await dalam isi metode. Jika pengkompilasi tidak menemukan ekspresi await, metode gagal menghasilkan. Meskipun compiler menghasilkan peringatan, kode masih dikompilasi dan pengkompilasi menjalankan metode . Komputer status yang dihasilkan oleh pengompilasi C# untuk metode asinkron tidak mencapai apa pun, sehingga seluruh proses sangat tidak efisien.
Tambahkan akhiran "Asinkron" ke nama metode asinkron
Konvensi gaya .NET adalah menambahkan akhiran "Asinkron" ke semua nama metode asinkron. Pendekatan ini membantu untuk lebih mudah membedakan antara metode sinkron dan asinkron. Metode tertentu yang tidak secara eksplisit dipanggil oleh kode Anda (seperti penanganan aktivitas atau metode pengontrol web) tidak selalu berlaku dalam skenario ini. Karena item ini tidak secara eksplisit dipanggil oleh kode Anda, menggunakan penamaan eksplisit tidak sepenting.
Mengembalikan 'asinkron void' hanya dari penanganan aktivitas
Penanganan aktivitas harus mendeklarasikan jenis pengembalian void dan tidak dapat menggunakan atau mengembalikan objek Task dan Task<T> seperti yang dilakukan metode lain. Saat Anda menulis penanganan aktivitas asinkron, Anda perlu menggunakan pengubah async pada metode pengembalian void untuk handler. Implementasi lain dari metode pengembalian async void tidak mengikuti model TAP dan dapat menghadirkan tantangan:
- Pengecualian yang dilemparkan dalam metode
async voidtidak dapat ditangkap di luar metode tersebut - metode
async voidsulit diuji - metode
async voiddapat menyebabkan efek samping negatif jika pemanggil tidak mengharapkannya menjadi asinkron
Berhati-hatilah dengan lambda asinkron di LINQ
Penting untuk berhati-hati saat Anda menerapkan lambda asinkron dalam ekspresi LINQ. Ekspresi Lambda dalam LINQ menggunakan eksekusi yang ditangguhkan, yang berarti kode dapat dijalankan pada waktu yang tidak terduga. Pengenalan tugas pemblokiran ke dalam skenario ini dapat dengan mudah mengakibatkan kebuntuan, jika kode tidak ditulis dengan benar. Selain itu, bersarangnya kode asinkron juga dapat menyulitkan pemahaman terhadap eksekusi kode. Asinkron dan LINQ sangat kuat, tetapi teknik ini harus digunakan bersama-sama dengan hati-hati dan sejelas mungkin.
Mengizinkan tugas dengan cara yang tidak memblokir
Jika program Anda memerlukan hasil tugas, tulis kode yang mengimplementasikan ekspresi await dengan cara yang tidak memblokir. Memblokade utas saat ini untuk menunggu item Task selesai secara sinkron dapat mengakibatkan deadlock dan utas konteks yang terblokir. Pendekatan pemrograman ini dapat memerlukan penanganan kesalahan yang lebih kompleks. Tabel berikut ini menyediakan panduan tentang cara mengakses hasil dari tugas dengan cara yang tidak memblokir:
| Skenario tugas | Kode saat ini | Ganti dengan 'tunggu' |
|---|---|---|
| Mengambil hasil dari tugas latar belakang |
Task.Wait atau Task.Result |
await |
| Lanjutkan saat tugas selesai | Task.WaitAny |
await Task.WhenAny |
| Lanjutkan saat semua tugas selesai | Task.WaitAll |
await Task.WhenAll |
| Lanjutkan setelah beberapa waktu | Thread.Sleep |
await Task.Delay |
Pertimbangkan untuk menggunakan tipe ValueTask
Ketika metode asinkron mengembalikan objek Task, hambatan kinerja mungkin terjadi di jalur tertentu. Karena Task adalah jenis referensi, objek Task dialokasikan dari timbunan. Jika metode yang dideklarasikan dengan modifier async mengembalikan hasil yang sudah di-cache atau menyelesaikan secara sinkron, alokasi tambahan bisa menyebabkan biaya waktu yang signifikan dalam bagian kode yang krusial terhadap performa. Skenario ini bisa menjadi pemborosan biaya ketika alokasi terjadi dalam perulangan yang sering. Untuk informasi selengkapnya, lihat jenis pengembalian asinkron umum.
Pahami kapan harus mengatur ConfigureAwait(false)
Pengembang sering menanyakan kapan harus menggunakan boolean Task.ConfigureAwait(Boolean). API ini memungkinkan instans Task untuk mengonfigurasi konteks untuk komputer status yang mengimplementasikan ekspresi await apa pun. Ketika boolean tidak diatur dengan benar, performa dapat menurun atau deadlock dapat terjadi. Untuk informasi selengkapnya, lihat tanya jawab umum ConfigureAwait.
Menulis kode dengan sedikit state
Hindari menulis kode yang bergantung pada status objek global atau eksekusi metode tertentu. Sebaliknya, hanya bergantung pada nilai yang dikembalikan oleh metode. Ada banyak manfaat dalam menulis kode yang lebih sedikit bergantung pada status:
- Lebih mudah untuk memahami kode
- Lebih mudah untuk menguji kode
- Lebih mudah untuk mencampur kode asinkron dan sinkron
- Dapat menghindari kondisi lomba dalam kode
- Sederhana untuk mengoordinasikan kode asinkron yang bergantung pada nilai yang dikembalikan
- (Bonus) Dapat bekerja dengan baik menggunakan injeksi dependensi dalam kode
Tujuan yang disarankan adalah untuk mencapai Transparansi Referensial yang lengkap atau hampir lengkap dalam kode Anda. Pendekatan ini menghasilkan basis kode yang dapat diprediksi, dapat diuji, dan dapat dipertahankan.
Akses sinkron ke operasi asinkron
Dalam skenario, Anda mungkin perlu memblokir operasi asinkron saat await kata kunci tidak tersedia di seluruh tumpukan panggilan Anda. Situasi ini terjadi dalam basis kode warisan atau ketika mengintegrasikan metode asinkron ke dalam API sinkron yang tidak dapat diubah.
Peringatan
Pemblokiran sinkron pada operasi asinkron dapat menyebabkan kebuntuan dan harus dihindari jika memungkinkan. Solusi yang disukai adalah menggunakan async/await di seluruh tumpukan panggilan Anda.
Ketika Anda harus memblokir secara sinkron pada Task, berikut adalah pendekatan yang tersedia, yang tercantum dari yang paling disukai hingga yang paling tidak disukai.
- Gunakan GetAwaiter(). GetResult()
- Gunakan Task.Run untuk skenario kompleks
- Gunakan Wait() dan Result
Gunakan GetAwaiter().GetResult()
Pola GetAwaiter().GetResult() umumnya adalah pendekatan yang disukai ketika Anda harus memblokir secara sinkron:
// When you cannot use await
Task<string> task = GetDataAsync();
string result = task.GetAwaiter().GetResult();
Pendekatan ini:
- Pertahankan pengecualian asli tanpa membungkusnya dalam
AggregateException. - Memblokir thread saat ini hingga tugas selesai sepenuhnya.
- Masih membawa risiko kebuntuan jika tidak digunakan dengan hati-hati.
Gunakan Task.Run untuk skenario kompleks
Untuk skenario kompleks di mana Anda perlu mengisolasi pekerjaan asinkron:
// Offload to thread pool to avoid context deadlocks
string result = Task.Run(async () => await GetDataAsync()).GetAwaiter().GetResult();
Pola ini:
- Menjalankan suatu metode asinkron pada utas di dalam kumpulan utas.
- Dapat membantu menghindari beberapa skenario kebuntuan.
- Menambahkan overhead dengan menjadwalkan tugas pada kumpulan utas.
Gunakan Wait() dan Result
Anda dapat menggunakan pendekatan pemblokiran dengan memanggil Wait() dan Result. Namun, pendekatan ini tidak disarankan karena membungkus pengecualian dalam AggregateException.
Task<string> task = GetDataAsync();
task.Wait();
string result = task.Result;
Masalah dengan Wait() dan Result:
- Pengecualian dibungkus dalam
AggregateException, membuat penanganan kesalahan lebih kompleks. - Risiko kebuntuan yang lebih tinggi.
- Niat kode yang kurang jelas.
Pertimbangan tambahan
- Pencegahan kebuntuan: Berhati-hatilah dalam aplikasi UI atau saat menggunakan konteks sinkronisasi.
- Dampak performa: Memblokir utas mengurangi skalabilitas.
- Penanganan pengecualian: Pengujian skenario kesalahan dengan hati-hati karena perilaku pengecualian berbeda antar pola.
Untuk panduan yang lebih rinci tentang tantangan dan pertimbangan pembungkus sinkron untuk metode asinkron, lihat Haruskah saya mengekspos pembungkus sinkron untuk metode asinkron?.
Tinjau contoh lengkap
Kode berikut menunjukkan contoh lengkap, yang tersedia dalam file contoh Program.cs.
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.AspNetCore.Mvc;
class Button
{
public Func<object, object, Task>? Clicked
{
get;
internal set;
}
}
class DamageResult
{
public int Damage
{
get { return 0; }
}
}
class User
{
public bool isEnabled
{
get;
set;
}
public int id
{
get;
set;
}
}
public class Program
{
private static readonly Button s_downloadButton = new();
private static readonly Button s_calculateButton = new();
private static readonly HttpClient s_httpClient = new();
private 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/dotnet/desktop/wpf/get-started/create-app-visual-studio",
"https://learn.microsoft.com/education",
"https://learn.microsoft.com/shows/net-core-101/what-is-net",
"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://dotnetfoundation.org",
"https://learn.microsoft.com/visualstudio",
"https://learn.microsoft.com/windows",
"https://learn.microsoft.com/maui"
};
private static void Calculate()
{
static DamageResult CalculateDamageDone()
{
return new DamageResult()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
};
}
s_calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};
}
private static void DisplayDamage(DamageResult damage)
{
Console.WriteLine(damage.Damage);
}
private static void Download(string URL)
{
s_downloadButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await s_httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};
}
private static void DoSomethingWithData(object stringData)
{
Console.WriteLine($"Displaying data: {stringData}");
}
private static async Task<User> GetUserAsync(int userId)
{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
return await Task.FromResult(new User() { id = userId });
}
private static async Task<IEnumerable<User>> GetUsersAsync(IEnumerable<int> userIds)
{
var getUserTasks = new List<Task<User>>();
foreach (int userId in userIds)
{
getUserTasks.Add(GetUserAsync(userId));
}
return await Task.WhenAll(getUserTasks);
}
private static async Task<User[]> GetUsersByLINQAsync(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
}
private static async Task ProcessTasksAsTheyCompleteAsync(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToList();
while (getUserTasks.Count > 0)
{
Task<User> completedTask = await Task.WhenAny(getUserTasks);
getUserTasks.Remove(completedTask);
User user = await completedTask;
Console.WriteLine($"Processed user {user.id}");
}
}
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCountAsync(string URL)
{
// Suspends GetDotNetCountAsync() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await s_httpClient.GetStringAsync(URL);
return Regex.Matches(html, @"\.NET").Count;
}
static async Task Main()
{
Console.WriteLine("Application started.");
Console.WriteLine("Counting '.NET' phrase in websites...");
int total = 0;
foreach (string url in s_urlList)
{
var result = await GetDotNetCountAsync(url);
Console.WriteLine($"{url}: {result}");
total += result;
}
Console.WriteLine("Total: " + total);
Console.WriteLine("Retrieving User objects with list of IDs...");
IEnumerable<int> ids = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var users = await GetUsersAsync(ids);
foreach (User? user in users)
{
Console.WriteLine($"{user.id}: isEnabled={user.isEnabled}");
}
Console.WriteLine("Processing tasks as they complete...");
await ProcessTasksAsTheyCompleteAsync(ids);
Console.WriteLine("Application ending.");
}
}
// Example output:
//
// Application started.
// Counting '.NET' phrase in websites...
// https://learn.microsoft.com: 0
// https://learn.microsoft.com/aspnet/core: 57
// https://learn.microsoft.com/azure: 1
// https://learn.microsoft.com/azure/devops: 2
// https://learn.microsoft.com/dotnet: 83
// https://learn.microsoft.com/dotnet/desktop/wpf/get-started/create-app-visual-studio: 31
// https://learn.microsoft.com/education: 0
// https://learn.microsoft.com/shows/net-core-101/what-is-net: 42
// https://learn.microsoft.com/enterprise-mobility-security: 0
// https://learn.microsoft.com/gaming: 0
// https://learn.microsoft.com/graph: 0
// https://learn.microsoft.com/microsoft-365: 0
// https://learn.microsoft.com/office: 0
// https://learn.microsoft.com/powershell: 0
// https://learn.microsoft.com/sql: 0
// https://learn.microsoft.com/surface: 0
// https://dotnetfoundation.org: 16
// https://learn.microsoft.com/visualstudio: 0
// https://learn.microsoft.com/windows: 0
// https://learn.microsoft.com/maui: 6
// Total: 238
// Retrieving User objects with list of IDs...
// 1: isEnabled= False
// 2: isEnabled= False
// 3: isEnabled= False
// 4: isEnabled= False
// 5: isEnabled= False
// 6: isEnabled= False
// 7: isEnabled= False
// 8: isEnabled= False
// 9: isEnabled= False
// 0: isEnabled= False
// Application ending.
Tautan terkait
- Model Pemrograman Asinkron Task (C#)