Bagikan melalui


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

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

Penamaan, parameter, dan jenis pengembalian

TAP menggunakan satu metode untuk mewakili inisiasi dan penyelesaian operasi asinkron. Ini berbeda 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 juga memerlukan satu atau beberapa event, jenis delegasi penanganan event, dan jenis turunan dari EventArg. Metode asinkron dalam TAP menyertakan Async akhiran setelah nama operasi untuk metode yang mengembalikan jenis yang dapat ditunda, seperti Task, Task<TResult>, ValueTask, dan ValueTask<TResult>. Misalnya, operasi asinkron Get yang mengembalikan Task<String> dapat diberi nama GetAsync. Jika Anda menambahkan metode TAP ke kelas yang sudah berisi nama metode EAP dengan Async akhiran, gunakan akhiran TaskAsync sebagai gantinya. Misalnya, jika kelas sudah memiliki GetAsync metode, gunakan nama GetTaskAsync. Jika metode memulai operasi asinkron tetapi tidak mengembalikan jenis yang dapat ditunggu, namanya harus dimulai dengan Begin, Start, atau beberapa kata kerja lain untuk menyarankan bahwa metode ini tidak mengembalikan atau melemparkan hasil operasi.  

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

Parameter metode TAP harus cocok dengan parameter rekan sinkronnya dan harus disediakan dalam urutan yang sama. Namun, out parameter dan ref dikecualikan dari aturan ini dan harus dihindari sepenuhnya. Setiap data yang akan dikembalikan melalui parameter out atau ref harus 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 menambahkan parameter CancellationToken meskipun versi sinkron dari metode TAP tidak menyediakannya.

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

Untuk contoh bagaimana sintaks TAP berbeda dari sintaks yang digunakan dalam pola pemrograman asinkron warisan 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 sejumlah kecil 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 jangka panjang 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 dilemparkan dari panggilan metode asinkron hanya sebagai respons terhadap kesalahan penggunaan. Kesalahan penggunaan tidak boleh terjadi dalam kode produksi. Misalnya, jika penerusan referensi null (Nothing di Visual Basic) sebagai argumen metode menyebabkan kondisi kesalahan (biasanya diwakili oleh pengecualian ArgumentNullException), Anda dapat memodifikasi kode panggilan untuk memastikan bahwa referensi null tidak pernah diteruskan. Untuk semua kesalahan lainnya, pengecualian yang terjadi ketika metode asinkron berjalan harus ditetapkan ke tugas yang dikembalikan, bahkan jika metode asinkron kebetulan selesai dengan cara sinkron sebelum tugas dikembalikan. Biasanya, tugas berisi paling banyak satu pengecualian. Namun, jika tugas mewakili beberapa operasi (misalnya, WhenAll), beberapa pengecualian dapat dikaitkan dengan satu tugas.

Lingkungan target

Saat menerapkan metode TAP, Anda dapat menentukan di mana eksekusi asinkron terjadi. Anda dapat memilih untuk menjalankan beban kerja pada kumpulan utas, menerapkannya 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 kondisi di tempat lain dalam sistem (misalnya, tugas yang mewakili data yang tiba pada struktur data yang diantrekan).

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

Status tugas

Kelas ini Task menyediakan siklus hidup untuk operasi asinkron, dan siklus tersebut TaskStatus diwakili oleh enumerasi. Untuk mendukung kasus-kasus khusus dari jenis yang diturunkan dari Task dan Task<TResult>, dan untuk mendukung pemisahan antara konstruksi dan 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 hanya dijadwalkan ketika Start dipanggil pada instans ini.

Semua tugas lain 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 menginstansiasi tugas yang akan dikembalikan, metode TAP harus memanggil objek StartTask sebelum mengembalikannya. Konsumen metode TAP dapat dengan aman mengasumsikan bahwa tugas yang dikembalikan aktif dan tidak boleh mencoba memanggil Start pada Task yang dihasilkan oleh metode TAP. Memanggil Start pada tugas aktif mengakibatkan InvalidOperationException pengecualian.

Pembatalan (opsional)

Di TAP, pembatalan bersifat opsional untuk pelaksana metode asinkron dan konsumen metode asinkron. Jika operasi memungkinkan pembatalan, operasi akan mengekspos kelebihan beban metode asinkron yang menerima token pembatalan (CancellationToken instans). Menurut 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 menerima permintaan pembatalan, sistem dapat memilih untuk mematuhi permintaan tersebut dan membatalkan operasi. Jika permintaan pembatalan mengakibatkan pekerjaan berakhir sebelum waktunya, metode TAP mengembalikan tugas yang berakhir dalam Canceled status; tidak ada hasil yang tersedia dan tidak ada pengecualian yang dilemparkan. Status Canceled dianggap sebagai status akhir (selesai) untuk tugas, bersama dengan status Faulted dan RanToCompletion . Oleh karena itu, jika tugas dalam status Canceled, propertinya IsCompleted mengembalikan true. Ketika tugas selesai dalam Canceled status, setiap kelanjutan yang terdaftar dengan tugas dijadwalkan atau dijalankan, kecuali opsi kelanjutan seperti NotOnCanceled ditentukan untuk menolak kelanjutan. Kode apa pun yang menunggu tugas yang dibatalkan secara asinkron melalui penggunaan fitur bahasa, masih akan berjalan dan menerima OperationCanceledException atau pengecualian yang diturunkan darinya. Kode yang diblokir secara sinkron menunggu tugas melalui metode seperti Wait dan WaitAll juga terus berjalan dengan pengecualian.

Jika token pembatalan telah meminta pembatalan sebelum metode TAP yang menerima token tersebut dipanggil, metode TAP harus mengembalikan Canceled tugas. 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 yang 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 default CancellationToken.

Pelaporan kemajuan (opsional)

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

Di TAP, kemajuan ditangani melalui IProgress<T> antarmuka, yang diteruskan ke metode asinkron sebagai parameter yang biasanya diberi nama progress. Menyediakan antarmuka kemajuan ketika metode asinkron disebut membantu menghilangkan kondisi balapan yang diakibatkan oleh penggunaan yang salah (yaitu, ketika penanganan aktivitas yang salah terdaftar setelah operasi dimulai dapat melewatkan pembaruan). Yang lebih penting, antarmuka kemajuan mendukung berbagai implementasi perkembangan, sesuai dengan yang ditentukan oleh kode yang menggunakan. Misalnya, kode yang menggunakan mungkin hanya peduli tentang pembaruan kemajuan terbaru, atau mungkin ingin menyimpan sementara semua pembaruan, atau mungkin ingin memanggil tindakan untuk setiap pembaruan, atau mungkin ingin mengontrol apakah pemanggilan dialihkan ke thread tertentu. Semua opsi ini dapat dicapai dengan menggunakan implementasi antarmuka yang berbeda, disesuaikan dengan kebutuhan konsumen tertentu. Seperti halnya pembatalan, implementasi TAP harus memberikan IProgress<T> parameter hanya jika API mendukung pemberitahuan kemajuan.

Misalnya, jika metode yang ReadAsync dibahas sebelumnya dalam artikel ini dapat melaporkan kemajuan sementara 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

FindFilesAsync Jika metode mengembalikan daftar semua file yang memenuhi pola pencarian tertentu, panggilan balik kemajuan dapat memberikan perkiraan persentase pekerjaan yang selesai dan kumpulan hasil parsial saat ini. Ini dapat memberikan informasi ini dengan salah satu dari 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 memberikan kelebihan beban yang menerima progress parameter, mereka harus mengizinkan argumen menjadi null, dalam hal ini tidak ada kemajuan yang dilaporkan. Implementasi TAP harus melaporkan kemajuan ke Progress<T> objek secara sinkron, yang memungkinkan metode asinkron untuk memberikan kemajuan dengan cepat. Ini juga memungkinkan penerima kemajuan untuk menentukan bagaimana dan di mana informasi sebaiknya ditangani. Misalnya, instance kemajuan dapat memilih untuk mengatur panggilan balik dan mengaktifkan peristiwa pada konteks sinkronisasi yang ditangkap.

Implementasi dari IProgress<T>

.NET menyediakan Progress<T> kelas , yang mengimplementasikan IProgress<T>. Kelas Progress<T> dinyatakan 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 dari Progress<T> mengungkapkan suatu peristiwa ProgressChanged, yang dipicu setiap kali operasi asinkron melaporkan pembaruan kemajuan. Peristiwa ProgressChanged dimunculkan pada objek SynchronizationContext yang ditangkap ketika instans Progress<T> dibuat. Jika tidak ada konteks sinkronisasi yang tersedia, konteks default yang menargetkan kumpulan utas digunakan. Handler dapat didaftarkan dengan kejadian ini. Sebuah handler tunggal dapat disediakan ke Progress<T> konstruktor untuk kemudahan, dan berfungsi seperti penanganan kejadian untuk acara ProgressChanged. Pembaruan kemajuan dimunculkan secara asinkron untuk menghindari penundaan operasi asinkron saat penanganan aktivitas dijalankan. Implementasi lain IProgress<T> 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 tidak memberikan 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, ini dapat 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, ini dapat mengekspos keempat kelebihan beban. Namun, ini hanya dapat 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 terlewat, pengembang dapat mengoper None atau default CancellationToken untuk parameter cancellationToken dan null untuk parameter progress.

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

Jika Anda memutuskan untuk mengekspos beberapa overload untuk membuat pembatalan atau kemajuan menjadi opsional, overload yang tidak mendukung pembatalan atau kemajuan harus berperilaku seolah-olah mereka memberikan None untuk pembatalan atau null untuk kemajuan ke overload 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 pengompilasi C# dan Visual Basic di Visual Studio, secara manual, atau melalui kombinasi pengompilasi dan metode manual.
Menggunakan Pola Asinkron Berbasis Tugas Menjelaskan bagaimana Anda dapat menggunakan tugas dan panggilan balik untuk menunggu tanpa harus 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).