Pola asinkron berbasis tugas (TAP) di .NET: Pengenalan dan gambaran umum

Di .NET, Pola asinkron berbasis tugas adalah pola dengan desain asinkron yang direkomendasikan untuk pengembangan baru. Ini didasarkan pada jenis Task dan Task<TResult> di namespace layanan System.Threading.Tasks, yang digunakan untuk mewakili operasi asinkron.

Penamaan, parameter, dan jenis pengembalian

TAP menggunakan satu metode untuk mewakili inisiasi dan penyelesaian operasi asinkron. Kontras ini dengan pola Model Pemrograman Asinkron (APM atau IAsyncResult) dan Pola Asinkron Berbasis Peristiwa (EAP). APM memerlukan metode Begin dan End. EAP memerlukan metode yang memiliki akhiran Async dan satu atau beberapa peristiwa, jenis delegasi penanganan aktivitas, dan jenis turunan EventArg. Metode asinkron dalam TAP menyertakan akhiran Async setelah nama operasi untuk metode yang mengembalikan jenis yang dapat ditunggu, seperti Task, Task<TResult>, ValueTask, dan ValueTask<TResult>. Misalnya, operasi Get asinkron yang mengembalikan Task<String> dapat diberi nama GetAsync. Jika Anda menambahkan metode TAP ke kelas yang sudah berisi nama metode EAP dengan akhiran Async, gunakan akhiran TaskAsync sebagai gantinya. Misalnya, jika kelas sudah memiliki metode GetAsync, gunakan nama GetTaskAsync. Jika suatu metode memulai operasi asinkron tetapi tidak mengembalikan jenis yang dapat ditunggu, namanya harus dimulai dengan Begin, Start, atau beberapa kata kerja lain untuk menunjukkan bahwa metode ini tidak mengembalikan atau membuang hasil operasi.  

Metode TAP mengembalikan System.Threading.Tasks.Task atau System.Threading.Tasks.Task<TResult>, berdasarkan apakah metode sinkron yang sesuai mengembalikan void atau jenis TResult.

Parameter metode TAP harus cocok dengan parameter rekan sinkronnya dan harus diberikan dalam urutan yang sama. Namun, parameter out dan ref dikecualikan dari aturan ini dan harus dihindari sepenuhnya. Setiap data yang akan dikembalikan melalui parameter out atau ref seharusnya dikembalikan sebagai bagian dari TResult yang dikembalikan oleh Task<TResult>, dan harus menggunakan tuple atau struktur data kustom untuk mengakomodasi beberapa nilai. Selain itu, pertimbangkan untuk menambahkan parameter CancellationToken bahkan jika rekan sinkron metode TAP tidak menawarkannya.

Metode yang dikhususkan secara eksklusif untuk pembuatan, manipulasi, atau kombinasi tugas (di mana niat asinkron metode tampak jelas dalam nama metode atau atas nama jenis tempat metode berada) tidak perlu mengikuti pola penamaan ini; metode tersebut sering disebut sebagai kombinator. Contoh kombinator termasuk WhenAll dan WhenAny, dan dibahas di bagian Menggunakan Kombinator Berbasis Tugas Bawaan dari artikel Menggunakan Pola Asinkron Berbasis Tugas.

Untuk contoh bagaimana sintaks TAP berbeda dari sintaks yang digunakan dalam pola pemrograman asinkron lama seperti Model Pemrograman Asinkron (APM) dan Pola Asinkron Berbasis Peristiwa (EAP), lihat Pola Pemrograman Asinkron.

Memulai operasi asinkron

Metode asinkron yang didasarkan pada TAP dapat melakukan sedikit pekerjaan secara sinkron, seperti memvalidasi argumen dan memulai operasi asinkron, sebelum mengembalikan tugas yang dihasilkan. Pekerjaan sinkron harus dijaga seminimal mungkin, sehingga metode asinkron dapat kembali dengan cepat. Alasan pengembalian cepat meliputi:

  • Metode asinkron dapat dipanggil dari utas antarmuka pengguna (UI) dan pekerjaan sinkron yang berjalan lama dapat membahayakan respons aplikasi.

  • Beberapa metode asinkron dapat diluncurkan secara bersamaan. Oleh karena itu, setiap pekerjaan yang berjalan lama dalam bagian sinkron dari metode asinkron dapat menunda inisiasi operasi asinkron lainnya, sehingga mengurangi manfaat konkurensi.

Dalam beberapa kasus, jumlah pekerjaan yang diperlukan untuk menyelesaikan operasi kurang dari jumlah pekerjaan yang diperlukan untuk meluncurkan operasi secara asinkron. Membaca dari aliran di mana operasi baca dapat dipenuhi oleh data yang sudah di-buffer dalam memori adalah contoh skenario seperti itu. Dalam kasus seperti itu, operasi dapat selesai secara sinkron dan dapat mengembalikan tugas yang telah selesai.

Pengecualian

Metode asinkron harus memunculkan pengecualian untuk dikeluarkan dari panggilan metode asinkron hanya sebagai respons terhadap kesalahan penggunaan. Kesalahan penggunaan tidak boleh terjadi dalam kode produksi. Misalnya, jika meneruskan referensi null (Nothing dalam Visual Basic) sebagai salah satu argumen metode menyebabkan status kesalahan (biasanya diwakili oleh pengecualian ArgumentNullException), Anda dapat memodifikasi kode panggilan untuk memastikan bahwa referensi null tidak pernah dilewatkan. Untuk semua kesalahan lainnya, pengecualian yang terjadi saat metode asinkron berjalan harus ditetapkan ke tugas yang dikembalikan, bahkan jika metode asinkron selesai secara sinkron sebelum tugas dikembalikan. Biasanya, sebuah tugas berisi paling banyak satu pengecualian. Namun, jika tugas mewakili beberapa operasi (misalnya, WhenAll), beberapa pengecualian dapat dikaitkan dengan satu tugas.

Lingkungan target

Saat Anda mengimplementasikan metode TAP, Anda dapat menentukan di mana eksekusi asinkron terjadi. Anda dapat memilih untuk menjalankan beban kerja pada kumpulan utas, mengimplementasikannya dengan menggunakan I/O asinkron (tanpa terikat ke utas untuk sebagian besar eksekusi operasi), menjalankannya pada utas tertentu (seperti utas UI), atau menggunakan sejumlah konteks potensial. Metode TAP bahkan mungkin tidak memiliki apa pun untuk dijalankan, dan hanya dapat mengembalikan Task yang mewakili terjadinya suatu kondisi di tempat lain dalam sistem (misalnya, tugas yang mewakili data yang tiba pada struktur data yang diantrekan).

Pemanggil metode TAP dapat memblokir penantian untuk metode TAP selesai dengan menunggu tugas yang dihasilkan secara sinkron, atau dapat menjalankan kode tambahan (lanjutan) saat operasi asinkron selesai. Pembuat kode lanjutan memiliki kontrol atas tempat kode tersebut dijalankan. Anda dapat membuat kode lanjutan, baik secara eksplisit melalui metode pada kelas Task (misalnya, ContinueWith) atau secara implisit, dengan menggunakan dukungan bahasa yang dibangun di atas lanjutan (misalnya, await di C#, Await di Visual Basic, AwaitValue di F#).

Status tugas

Kelas Task menyediakan siklus hidup untuk operasi asinkron dan siklus tersebut diwakili oleh enumerasi TaskStatus. Untuk mendukung kasus sudut jenis yang berasal dari Task dan Task<TResult>, dan mendukung pemisahan konstruksi dari penjadwalan, kelas Task mengekspos metode Start. Tugas yang dibuat oleh konstruktor publik Task disebut sebagai tugas dingin, karena mereka memulai siklus hidup mereka dalam status tidak terjadwal Created dan dijadwalkan hanya saat Start dipanggil pada instans ini.

Semua tugas lainnya memulai siklus hidup mereka dalam keadaan panas, yang berarti bahwa operasi asinkron yang mereka wakili telah dimulai dan status tugas mereka adalah nilai enumerasi selain TaskStatus.Created. Semua tugas yang dikembalikan dari metode TAP harus diaktifkan. Jika metode TAP secara internal menggunakan konstruktor tugas untuk membuat instans tugas yang akan dikembalikan, metode TAP harus memanggil Start pada objek Task sebelum mengembalikannya. Konsumen metode TAP dapat dengan aman mengasumsikan bahwa tugas yang dikembalikan aktif dan tidak boleh mencoba memanggil Start pada Task apa pun yang dikembalikan dari metode TAP. Memanggil Start pada tugas yang aktif menghasilkan pengecualian InvalidOperationException.

Pembatalan (opsional)

Di TAP, pembatalan bersifat opsional untuk pelaksana dan konsumen metode asinkron. Jika suatu operasi mengizinkan pembatalan, operasi tersebut akan mengekspos kelebihan beban dari metode asinkron yang menerima token pembatalan (instans CancellationToken). Berdasarkan konvensi, parameter diberi nama cancellationToken.

public Task ReadAsync(byte [] buffer, int offset, int count,
                      CancellationToken cancellationToken)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
                          count As Integer,
                          cancellationToken As CancellationToken) _
                          As Task

Operasi asinkron memantau token ini untuk permintaan pembatalan. Jika token ini menerima permintaan pembatalan, demikian dapat memilih untuk mematuhi permintaan tersebut dan membatalkan operasi. Jika permintaan pembatalan mengakibatkan pekerjaan berakhir sebelum waktunya, metode TAP mengembalikan tugas yang berakhir pada status Canceled; tidak ada hasil yang tersedia dan tidak ada pengecualian yang dikeluarkan. Status Canceled dianggap sebagai status akhir (selesai) untuk tugas, bersama dengan status Faulted dan RanToCompletion. Oleh karena itu, jika tugas dalam status Canceled, properti IsCompleted-nya mengembalikan true. Saat sebuah tugas selesai dalam status Canceled, setiap lanjutan yang terdaftar dengan tugas dijadwalkan atau dijalankan, kecuali opsi lanjutan seperti NotOnCanceled ditentukan untuk ditolak dari lanjutan. Kode apa pun yang secara asinkron menunggu tugas yang dibatalkan melalui penggunaan fitur bahasa terus berjalan, tetapi menerima OperationCanceledException atau pengecualian yang diturunkan darinya. Kode yang diblokir secara sinkron menunggu tugas melalui metode seperti Wait dan WaitAll, serta terus berjalan dengan pengecualian.

Jika token pembatalan telah meminta pembatalan sebelum metode TAP yang menerima token tersebut dipanggil, metode TAP harus mengembalikan tugas Canceled. Namun, jika pembatalan diminta saat operasi asinkron berjalan, operasi asinkron tidak perlu menerima permintaan pembatalan. Tugas yang dikembalikan harus berakhir dalam status Canceled hanya jika operasi berakhir sebagai akibat dari permintaan pembatalan. Jika pembatalan diminta tetapi hasil atau pengecualian masih dihasilkan, tugas harus berakhir dalam status RanToCompletion atau Faulted.

Untuk metode asinkron yang ingin mengekspos kemampuan untuk dibatalkan terlebih dahulu dan terpenting, Anda tidak perlu memberikan kelebihan beban yang tidak menerima token pembatalan. Untuk metode yang tidak dapat dibatalkan, jangan berikan kelebihan beban yang menerima token pembatalan; ini membantu menunjukkan kepada pemanggil apakah metode target benar-benar dapat dibatalkan. Kode konsumen yang tidak menginginkan pembatalan dapat memanggil metode yang menerima CancellationToken dan memberikan None sebagai nilai argumen. None secara fungsional setara dengan CancellationToken default.

Pelaporan kemajuan (opsional)

Beberapa operasi asinkron mendapat manfaat dari memberikan pemberitahuan kemajuan; hal ini biasanya digunakan untuk memperbarui antarmuka pengguna dengan informasi tentang kemajuan operasi asinkron.

Di TAP, kemajuan dihandel melalui antarmuka IProgress<T>, yang diteruskan ke metode asinkron sebagai parameter yang biasanya diberi nama progress. Menyediakan antarmuka kemajuan saat metode asinkron dipanggil membantu menghilangkan kondisi balapan yang dihasilkan dari penggunaan yang salah (yaitu, saat penanganan aktivitas yang salah didaftarkan setelah operasi dimulai mungkin melewatkan pembaruan). Yang lebih penting, antarmuka kemajuan mendukung berbagai implementasi kemajuan, seperti yang ditentukan oleh kode yang digunakan. Misalnya, kode yang digunakan mungkin hanya peduli dengan pembaruan kemajuan terbaru, atau mungkin ingin mem-buffer semua pembaruan, atau mungkin ingin memanggil tindakan untuk setiap pembaruan, atau mungkin ingin mengontrol apakah pemanggilan dimarshal ke utas tertentu. Semua opsi ini dapat dicapai dengan menggunakan implementasi dari antarmuka yang berbeda, disesuaikan dengan kebutuhan konsumen tertentu. Layaknya pembatalan, implementasi TAP harus menyediakan parameter IProgress<T> hanya jika API mendukung pemberitahuan kemajuan.

Misalnya, jika metode ReadAsync yang dibahas sebelumnya dalam artikel ini dapat melaporkan kemajuan menengah dalam bentuk jumlah byte yang dibaca sejauh ini, panggilan balik kemajuan dapat berupa antarmuka IProgress<T>:

public Task ReadAsync(byte[] buffer, int offset, int count,
                      IProgress<long> progress)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
                          count As Integer,
                          progress As IProgress(Of Long)) As Task

Jika metode FindFilesAsync mengembalikan daftar semua file yang memenuhi pola pencarian tertentu, panggilan balik kemajuan dapat memberikan perkiraan persentase dari pekerjaan yang selesai dan set hasil parsial saat ini. Ini dapat memberikan informasi ini dengan tuple:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
            string pattern,
            IProgress<Tuple<double,
            ReadOnlyCollection<List<FileInfo>>>> progress)
Public Function FindFilesAsync(pattern As String,
                               progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) _
                               As Task(Of ReadOnlyCollection(Of FileInfo))

atau dengan jenis data yang khusus untuk API:

public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
    string pattern,
    IProgress<FindFilesProgressInfo> progress)
Public Function FindFilesAsync(pattern As String,
                               progress As IProgress(Of FindFilesProgressInfo)) _
                               As Task(Of ReadOnlyCollection(Of FileInfo))

Dalam kasus terakhir, jenis data khusus biasanya diakhiri dengan ProgressInfo.

Jika implementasi TAP menyediakan kelebihan beban yang menerima parameter progress, mereka harus mengizinkan argumen menjadi null, dalam hal ini tidak ada kemajuan yang dilaporkan. Implementasi TAP harus melaporkan kemajuan ke objek Progress<T> secara sinkron, yang memungkinkan metode asinkron untuk memberikan kemajuan dengan cepat. Hal ini juga memungkinkan konsumen dari kemajuan untuk menentukan bagaimana dan di manakah yang terbaik untuk menghandel informasi. Misalnya, instans kemajuan dapat memilih untuk melakukan panggilan balik marshal dan meningkatkan peristiwa pada konteks sinkronisasi yang diambil.

Implementasi IProgress<T>

.NET menyediakan kelas Progress<T>, yang mengimplementasikan IProgress<T>. Kelas Progress<T> dideklarasikan sebagai berikut:

public class Progress<T> : IProgress<T>  
{  
    public Progress();  
    public Progress(Action<T> handler);  
    protected virtual void OnReport(T value);  
    public event EventHandler<T>? ProgressChanged;  
}  

Instans Progress<T> mengekspos peristiwa ProgressChanged, yang dimunculkan setiap kali operasi asinkron melaporkan pembaruan kemajuan. Peristiwa ProgressChanged dimunculkan pada objek SynchronizationContext yang ditangkap saat instans Progress<T> dibuat. Jika tidak ada konteks sinkronisasi yang tersedia, konteks default yang menargetkan kumpulan utas akan digunakan. Penangan dapat didaftarkan dengan peristiwa ini. Penangan tunggal juga dapat diberikan kepada konstruktor Progress<T> untuk kenyamanan, dan berperilaku seperti penanganan aktivitas untuk acara ProgressChanged tersebut. Pembaruan kemajuan dimunculkan secara asinkron untuk menghindari penundaan operasi asinkron saat penangan aktivitas dijalankan. Implementasi IProgress<T> lainnya dapat memilih untuk menerapkan semantik yang berbeda.

Memilih kelebihan beban yang akan disediakan

Jika implementasi TAP menggunakan parameter opsional CancellationToken dan opsional IProgress<T>, hal ini berpotensi memerlukan hingga empat kelebihan beban:

public Task MethodNameAsync(…);  
public Task MethodNameAsync(…, CancellationToken cancellationToken);  
public Task MethodNameAsync(…, IProgress<T> progress);
public Task MethodNameAsync(…,
    CancellationToken cancellationToken, IProgress<T> progress);  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, cancellationToken As CancellationToken cancellationToken) As Task  
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
                       progress As IProgress(Of T)) As Task  

Namun, banyak implementasi TAP yang tidak menyediakan kemampuan pembatalan atau kemajuan, sehingga memerlukan satu metode:

public Task MethodNameAsync(…);  
Public MethodNameAsync(…) As Task  

Jika implementasi TAP mendukung pembatalan atau kemajuan tetapi tidak keduanya, demikian mungkin memberikan dua kelebihan beban:

public Task MethodNameAsync(…);  
public Task MethodNameAsync(…, CancellationToken cancellationToken);  
  
// … or …  
  
public Task MethodNameAsync(…);  
public Task MethodNameAsync(…, IProgress<T> progress);  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, cancellationToken As CancellationToken) As Task  
  
' … or …  
  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task  

Jika implementasi TAP mendukung pembatalan dan kemajuan, demikian dapat mengekspos keempat kelebihan beban. Namun, implementasi tersebut mungkin hanya menyediakan dua hal berikut:

public Task MethodNameAsync(…);  
public Task MethodNameAsync(…,
    CancellationToken cancellationToken, IProgress<T> progress);  
Public MethodNameAsync(…) As Task  
Public MethodNameAsync(…, cancellationToken As CancellationToken,
                       progress As IProgress(Of T)) As Task  

Untuk mengimbangi dua kombinasi perantara yang hilang, pengembang dapat meneruskan None atau CancellationToken default untuk parameter cancellationToken dan null untuk parameter progress.

Jika Anda mengharapkan setiap penggunaan metode TAP mendukung pembatalan atau kemajuan, Anda dapat menghilangkan kelebihan beban yang tidak menerima parameter yang relevan.

Jika Anda memutuskan untuk mengekspos beberapa kelebihan beban untuk membuat pembatalan atau kemajuan opsional, kelebihan beban yang tidak mendukung pembatalan atau kemajuan harus berperilaku seolah-olah mereka meneruskan None untuk pembatalan atau null untuk kemajuan ke kelebihan beban yang mendukung ini.

Judul Deskripsi
Pola Pemrograman Asinkron Memperkenalkan tiga pola untuk melakukan operasi asinkron: Pola Asinkron Berbasis Tugas (TAP), Model Pemrograman Asinkron (APM), dan Pola Asinkron Berbasis Peristiwa (EAP).
Menerapkan Pola Asinkron Berbasis Tugas Menjelaskan cara mengimplementasikan Pola Asinkron Berbasis Tugas (TAP) dalam tiga cara: dengan menggunakan kompiler C# dan Visual Basic di Visual Studio, secara manual, atau melalui kombinasi metode kompiler dan manual.
Mengonsumsi Pola Asinkron Berbasis Tugas Menjelaskan bagaimana Anda dapat menggunakan tugas dan panggilan balik untuk mencapai penantian tanpa memblokir.
Interop dengan Pola dan Jenis Asinkron Lainnya Menjelaskan cara menggunakan Pola Asinkron Berbasis Tugas (TAP) untuk mengimplementasikan Model Pemrograman Asinkron (APM) dan Pola Asinkron Berbasis Peristiwa (EAP).