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.
Anda dapat mengimplementasikan Pola Asinkron berbasis Tugas (TAP) dengan tiga cara: dengan menggunakan pengompilasi C# dan Visual Basic di Visual Studio, secara manual, atau melalui kombinasi pengompilasi dan metode manual. Bagian berikut membahas setiap metode secara rinci. Anda dapat menggunakan pola TAP untuk mengimplementasikan operasi asinkron yang terikat pada komputasi maupun terikat pada I/O. Bagian Beban Kerja membahas setiap jenis operasi.
Membuat metode TAP
Menggunakan pengkompilasi
Dimulai dengan .NET Framework 4.5, metode apa pun yang dikaitkan dengan async kata kunci (Async dalam Visual Basic) dianggap sebagai metode asinkron, dan pengompilasi C# dan Visual Basic melakukan transformasi yang diperlukan untuk mengimplementasikan metode secara asinkron dengan menggunakan TAP. Metode asinkron harus mengembalikan System.Threading.Tasks.Task objek atau System.Threading.Tasks.Task<TResult> . Untuk yang terakhir, isi fungsi harus mengembalikan TResult, dan pengkompilasi memastikan bahwa hasil ini tersedia melalui objek tugas yang dihasilkan. Demikian pula, setiap pengecualian yang tidak tertangani dalam isi metode diteruskan ke tugas output dan menyebabkan tugas yang dihasilkan berakhir dalam status TaskStatus.Faulted. Pengecualian untuk aturan ini adalah ketika OperationCanceledException (atau jenis turunan) tidak tertangani, dalam hal ini tugas yang dihasilkan berakhir dalam TaskStatus.Canceled status.
Membuat metode TAP secara manual
Anda dapat menerapkan pola TAP secara manual untuk kontrol yang lebih baik atas implementasi. Pengkompilasi bergantung pada area antarmuka publik yang diekspos dari System.Threading.Tasks namespace dan jenis pendukung di System.Runtime.CompilerServices namespace. Untuk mengimplementasikan TAP sendiri, Anda membuat TaskCompletionSource<TResult> objek, melakukan operasi asinkron, dan ketika selesai, panggil SetResultmetode , SetException, atau SetCanceled , atau Try versi salah satu metode ini. Saat menerapkan metode TAP secara manual, Anda harus menyelesaikan tugas yang dihasilkan saat operasi asinkron yang diwakili selesai. Contohnya:
public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, ar =>
{
try { tcs.SetResult(stream.EndRead(ar)); }
catch (Exception exc) { tcs.SetException(exc); }
}, state);
return tcs.Task;
}
<Extension()>
Public Function ReadTask(stream As Stream, buffer() As Byte,
offset As Integer, count As Integer,
state As Object) As Task(Of Integer)
Dim tcs As New TaskCompletionSource(Of Integer)()
stream.BeginRead(buffer, offset, count, Sub(ar)
Try
tcs.SetResult(stream.EndRead(ar))
Catch exc As Exception
tcs.SetException(exc)
End Try
End Sub, state)
Return tcs.Task
End Function
Pendekatan hibrid
Anda mungkin merasa berguna untuk menerapkan pola TAP secara manual tetapi untuk mendelegasikan logika inti untuk implementasi ke pengkompilasi. Misalnya, Anda mungkin ingin menggunakan pendekatan hibrid ketika Anda ingin memverifikasi argumen di luar metode asinkron yang dihasilkan kompilator sehingga pengecualian dapat lolos ke pemanggil langsung metode daripada diekspos melalui System.Threading.Tasks.Task objek:
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException("input");
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
return value;
}
Public Function MethodAsync(input As String) As Task(Of Integer)
If input Is Nothing Then Throw New ArgumentNullException("input")
Return MethodAsyncInternal(input)
End Function
Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)
' code that uses await goes here
return value
End Function
Kasus lain di mana delegasi tersebut berguna adalah ketika Anda menerapkan pengoptimalan jalur cepat dan ingin mengembalikan tugas yang di-cache.
Beban Kerja
Anda dapat menerapkan operasi asinkron terikat komputasi maupun I/O sebagai metode TAP. Namun, ketika metode TAP diekspos secara publik dari pustaka, metode tersebut harus disediakan hanya untuk beban kerja yang melibatkan operasi terikat I/O (mereka mungkin juga melibatkan komputasi, tetapi tidak boleh murni komputasi). Jika metode murni terikat komputasi, metode harus diekspos hanya sebagai implementasi sinkron. Kode yang mengonsumsinya kemudian dapat memilih apakah akan membalut pemanggilan metode sinkronis tersebut ke dalam suatu tugas untuk memindahkan pekerjaan ke utas lain atau untuk mencapai paralelisme. Dan jika metode terikat I/O, metode tersebut harus diekspos hanya sebagai implementasi asinkron.
Tugas yang terbatas oleh komputasi
Kelas System.Threading.Tasks.Task ini sangat cocok untuk mewakili operasi intensif komputasi. Secara default, ia memanfaatkan dukungan khusus dalam ThreadPool kelas untuk memberikan eksekusi yang efisien, dan juga memberikan kontrol yang signifikan atas kapan, di mana, dan bagaimana komputasi asinkron dijalankan.
Anda dapat membuat tugas yang terikat komputasi dengan cara berikut:
Di .NET Framework 4.5 dan versi yang lebih baru (termasuk .NET Core dan .NET 5+), gunakan metode statis Task.Run sebagai pintasan ke TaskFactory.StartNew. Anda dapat dengan mudah menggunakan Run untuk meluncurkan tugas yang bergantung pada komputasi dan menargetkan kumpulan utas. Ini adalah mekanisme yang disukai untuk meluncurkan tugas yang terikat komputasi. Gunakan
StartNewsecara langsung hanya ketika Anda menginginkan kontrol yang lebih halus atas tugas.Di .NET Framework 4, gunakan metode TaskFactory.StartNew, yang menerima delegat (biasanya Action<T> atau Func<TResult>) untuk dieksekusi secara asinkron. Jika Anda menyediakan Action<T> delegasi, metode tersebut mengembalikan System.Threading.Tasks.Task objek yang mewakili eksekusi asinkron dari delegasi tersebut. Jika Anda memberikan Func<TResult> delegasi, metode mengembalikan System.Threading.Tasks.Task<TResult> objek. Overload dari metode StartNew menerima token pembatalan (CancellationToken), opsi pembuatan tugas (TaskCreationOptions), dan penjadwal tugas (TaskScheduler), yang semuanya memungkinkan kontrol terperinci atas penjadwalan dan eksekusi tugas. Instans pabrik yang menargetkan penjadwal tugas saat ini tersedia sebagai properti statis (Factory) dari kelas Task; misalnya:
Task.Factory.StartNew(…).Gunakan konstruktor jenis
TaskdanStartmetode jika Anda ingin membuat dan menjadwalkan tugas secara terpisah. Metode publik hanya boleh mengembalikan tugas yang telah dimulai.Gunakan kelebihan beban Task.ContinueWith metode. Metode ini membuat tugas baru yang dijadwalkan ketika tugas lain selesai. ContinueWith Beberapa kelebihan beban menerima token pembatalan, opsi kelanjutan, dan penjadwal tugas untuk kontrol yang lebih baik atas penjadwalan dan eksekusi tugas kelanjutan.
Gunakan metode TaskFactory.ContinueWhenAll dan TaskFactory.ContinueWhenAny. Metode ini membuat tugas baru yang dijadwalkan ketika semua atau salah satu dari sekumpulan tugas yang disediakan selesai. Metode ini juga menyediakan kelebihan beban untuk mengontrol penjadwalan dan eksekusi tugas-tugas ini.
Dalam tugas yang terikat komputasi, sistem dapat mencegah eksekusi tugas terjadwal jika menerima permintaan pembatalan sebelum mulai menjalankan tugas. Dengan demikian, jika Anda memberikan token pembatalan (CancellationToken objek), Anda dapat meneruskan token tersebut ke kode asinkron yang memantau token. Anda juga dapat memberikan token ke salah satu metode yang disebutkan sebelumnya seperti StartNew atau Run sehingga Task runtime juga dapat memantau token.
Misalnya, pertimbangkan metode asinkron yang merender gambar. Isi pekerjaan dapat memantau token pembatalan sehingga kode dapat keluar lebih awal jika permintaan pembatalan tiba saat proses render. Selain itu, jika permintaan pembatalan tiba sebelum penyajian dimulai, Anda harus mencegah operasi penyajian:
internal Task<Bitmap> RenderAsync(
ImageData data, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
var bmp = new Bitmap(data.Width, data.Height);
for(int y=0; y<data.Height; y++)
{
cancellationToken.ThrowIfCancellationRequested();
for(int x=0; x<data.Width; x++)
{
// render pixel [x,y] into bmp
}
}
return bmp;
}, cancellationToken);
}
Friend Function RenderAsync(data As ImageData, cancellationToken As _
CancellationToken) As Task(Of Bitmap)
Return Task.Run(Function()
Dim bmp As New Bitmap(data.Width, data.Height)
For y As Integer = 0 to data.Height - 1
cancellationToken.ThrowIfCancellationRequested()
For x As Integer = 0 To data.Width - 1
' render pixel [x,y] into bmp
Next
Next
Return bmp
End Function, cancellationToken)
End Function
Tugas terikat komputasi berakhir dalam status Canceled jika setidaknya salah satu kondisi berikut ini benar:
Permintaan pembatalan tiba melalui CancellationToken objek, yang disediakan sebagai argumen untuk metode pembuatan (misalnya,
StartNewatauRun) sebelum tugas beralih ke Running status.Ada pengecualian OperationCanceledException yang tidak tertangani dalam isi tugas tersebut; pengecualian itu berisi hal yang sama CancellationToken yang diteruskan ke tugas, dan token tersebut menunjukkan bahwa pembatalan telah diminta.
Jika pengecualian lain tidak tertangani dalam isi tugas, tugas berakhir dalam Faulted status, dan upaya apa pun untuk menunggu tugas atau mengakses hasilnya menyebabkan pengecualian dilemparkan.
Tugas yang bergantung pada I/O
Untuk membuat tugas yang tidak boleh langsung didukung oleh utas untuk keseluruhan eksekusinya, gunakan jenis TaskCompletionSource<TResult>. Jenis ini mengekspos properti Task yang mengembalikan instans Task<TResult> yang terkait. Siklus hidup tugas ini dikendalikan oleh TaskCompletionSource<TResult> metode seperti SetResult, SetException, SetCanceled, dan varian-variannya TrySet.
Katakanlah Anda ingin membuat tugas yang akan selesai setelah jangka waktu tertentu. Misalnya, Anda mungkin ingin menunda aktivitas di antarmuka pengguna. Kelas System.Threading.Timer sudah menyediakan kemampuan untuk secara asinkron memanggil delegasi setelah periode waktu tertentu, dan dengan menggunakan TaskCompletionSource<TResult> Anda dapat menempatkan Task<TResult> front pada timer, misalnya:
public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
TaskCompletionSource<DateTimeOffset> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(DateTimeOffset.UtcNow);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<DateTimeOffset>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset)
Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing
Dim timer As Timer = Nothing
timer = New Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(DateTimeOffset.UtcNow)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of DateTimeOffset)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
Metode Task.Delay ini disediakan untuk tujuan ini, dan Anda dapat menggunakannya di dalam metode asinkron lain, misalnya, untuk mengimplementasikan perulangan polling asinkron:
public static async Task Poll(Uri url, CancellationToken cancellationToken,
IProgress<bool> progress)
{
while(true)
{
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
bool success = false;
try
{
await DownloadStringAsync(url);
success = true;
}
catch { /* ignore errors */ }
progress.Report(success);
}
}
Public Async Function Poll(url As Uri, cancellationToken As CancellationToken,
progress As IProgress(Of Boolean)) As Task
Do While True
Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)
Dim success As Boolean = False
Try
await DownloadStringAsync(url)
success = true
Catch
' ignore errors
End Try
progress.Report(success)
Loop
End Function
Kelas TaskCompletionSource<TResult> tidak memiliki rekan non-generik. Namun, Task<TResult> berasal dari Task, sehingga Anda dapat menggunakan objek generik TaskCompletionSource<TResult> untuk metode terikat I/O yang hanya mengembalikan tugas. Untuk melakukan ini, Anda dapat menggunakan sumber dengan dummy TResult (Boolean adalah pilihan default yang baik, tetapi jika Anda khawatir pengguna Task akan melakukan downcasting ke Task<TResult>, Anda dapat menggunakan tipe privat TResult sebagai gantinya). Misalnya, Delay metode dalam contoh sebelumnya mengembalikan waktu saat ini bersama dengan offset yang dihasilkan (Task<DateTimeOffset>). Jika nilai hasil seperti itu tidak perlu, metode tersebut dapat dikodekan sebagai berikut (perhatikan perubahan jenis pengembalian dan perubahan argumen menjadi TrySetResult):
public static Task<bool> Delay(int millisecondsTimeout)
{
TaskCompletionSource<bool> tcs = null;
Timer timer = null;
timer = new Timer(delegate
{
timer.Dispose();
tcs.TrySetResult(true);
}, null, Timeout.Infinite, Timeout.Infinite);
tcs = new TaskCompletionSource<bool>(timer);
timer.Change(millisecondsTimeout, Timeout.Infinite);
return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of Boolean)
Dim tcs As TaskCompletionSource(Of Boolean) = Nothing
Dim timer As Timer = Nothing
Timer = new Timer(Sub(obj)
timer.Dispose()
tcs.TrySetResult(True)
End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)
tcs = New TaskCompletionSource(Of Boolean)(timer)
timer.Change(millisecondsTimeout, Timeout.Infinite)
Return tcs.Task
End Function
Tugas yang memerlukan kombinasi komputasi dan input/output (I/O)
Metode asinkron tidak terbatas hanya pada operasi terikat komputasi atau terikat I/O tetapi dapat mewakili campuran keduanya. Bahkan, beberapa operasi asinkron sering digabungkan menjadi operasi campuran yang lebih besar. Misalnya, metode RenderAsync yang disebutkan dalam contoh sebelumnya melakukan operasi komputasi intensif untuk merender gambar berdasarkan input tertentu imageData. Ini imageData bisa berasal dari layanan web yang Anda akses secara asinkron:
public async Task<Bitmap> DownloadDataAndRenderImageAsync(
CancellationToken cancellationToken)
{
var imageData = await DownloadImageDataAsync(cancellationToken);
return await RenderAsync(imageData, cancellationToken);
}
Public Async Function DownloadDataAndRenderImageAsync(
cancellationToken As CancellationToken) As Task(Of Bitmap)
Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken)
Return Await RenderAsync(imageData, cancellationToken)
End Function
Contoh ini juga menunjukkan bagaimana token pembatalan tunggal dapat dilewatkan melalui beberapa operasi asinkron. Untuk informasi selengkapnya, lihat bagian penggunaan pembatalan dalam Mengonsumsi Pola Asinkron berbasis Tugas.