Tugas turunan Terlampir dan Dicopot
Tugas turunan (atau tugas berlapis) adalah System.Threading.Tasks.Task instans yang dibuat di delegasi pengguna dari tugas lain, yang dikenal sebagai tugas induk. Tugas turunan dapat dilepas atau dilampirkan. Tugas turunan yang dilepas adalah tugas yang dijalankan secara terpisah dari induknya. Tugas turunan yang dilampirkan adalah tugas berlapis yang dibuat dengan opsi TaskCreationOptions.AttachedToParent yang induknya tidak secara eksplisit atau secara default melarangnya dilampirkan. Tugas dapat membuat sejumlah tugas anak yang dilampirkan dan dilepas, hanya dibatasi oleh sumber daya sistem.
Tabel berikut ini mencantumkan perbedaan dasar antara dua jenis tugas turunan tersebut.
Kategori | Tugas turunan yang dilepas | Tugas turunan yang dilampirkan |
---|---|---|
Induk menunggu tugas anakan selesai. | Tidak | Ya |
Induk menyebarluaskan pengecualian yang dilemparkan oleh tugas turunan. | Tidak | Ya |
Status induk tergantung pada status turunannya. | Tidak | Ya |
Dalam sebagian besar skenario, kami sarankan Anda menggunakan tugas turunan yang dilepas, karena hubungannya dengan tugas lain kurang kompleks. Itulah sebabnya tugas yang dibuat di dalam tugas induk dilepas secara default, dan Anda harus secara eksplisit menentukan opsi TaskCreationOptions.AttachedToParent untuk membuat tugas turunan yang dilampirkan.
Tugas turunan yang dilepas
Meskipun tugas turunan dibuat oleh tugas induk, secara default tugas tersebut tidak bergantung pada tugas induk. Dalam contoh berikut, tugas induk membuat satu tugas turunan sederhana. Jika Anda menjalankan kode contoh beberapa kali, Anda mungkin melihat bahwa output dari contoh berbeda dari yang ditampilkan, dan output itu bisa berubah setiap kali Anda menjalankan kode. Ini terjadi karena tugas induk dan tugas turunan dijalankan secara independen satu sama lain; tugas turunan adalah tugas yang dilepaskan. Contoh ini hanya menunggu tugas induk selesai, dan tugas anak mungkin tidak dijalankan atau diselesaikan sebelum aplikasi konsol berakhir.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});
parent.Wait();
Console.WriteLine("Outer has completed.");
}
}
// The example produces output like the following:
// Outer task executing.
// Nested task starting.
// Outer has completed.
// Nested task completing.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(500000)
Console.WriteLine("Nested task completing.")
End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
End Module
' The example produces output like the following:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.
Jika tugas anak diwakili oleh objek Task<TResult>, bukan objek Task, Anda dapat memastikan bahwa tugas induk akan menunggu tugas anak selesai dengan mengakses properti Task<TResult>.Result dari anak meskipun itu adalah tugas turunan yang dilepaskan. Properti Result memblokir hingga tugasnya selesai, seperti yang ditunjukkan contoh berikut.
using System;
using System.Threading;
using System.Threading.Tasks;
class Example
{
static void Main()
{
var outer = Task<int>.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");
var nested = Task<int>.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Nested task completing.");
return 42;
});
// Parent will wait for this detached child.
return nested.Result;
});
Console.WriteLine("Outer has returned {0}.", outer.Result);
}
}
// The example displays the following output:
// Outer task executing.
// Nested task starting.
// Nested task completing.
// Outer has returned 42.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Nested task starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Nested task completing.")
Return 42
End Function)
Return child.Result
End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
End Module
' The example displays the following output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
Tugas turunan yang dilampirkan
Tidak seperti tugas turunan yang dilepas, tugas anak yang dilampirkan disinkronkan secara erat dengan induknya. Anda bisa mengubah tugas turunan yang dilepas dalam contoh sebelumnya ke tugas turunan yang dilampirkan dengan menggunakan opsi TaskCreationOptions.AttachedToParent dalam pernyataan pembuatan tugas, seperti yang diperlihatkan dalam contoh berikut. Dalam kode ini, tugas anak yang dilampirkan selesai sebelum induknya. Akibatnya, output dari contoh sama setiap kali Anda menjalankan kode.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays the following output:
// Parent task executing.
// Attached child starting.
// Attached child completing.
// Parent has completed.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task executing")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays the following output:
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.
Anda dapat menggunakan tugas anak terlampir untuk membuat grafik operasi yang disingkronkan dengan ketak dari operasi asinkron.
Namun, tugas anak dapat menempel ke induknya hanya jika induknya tidak melarang tugas turunan terlampir. Tugas induk dapat secara eksplisit mencegah tugas anak melampirkannya dengan menentukan TaskCreationOptions.DenyChildAttach opsi pada konstruktor kelas tugas induk atau metode TaskFactory.StartNew. Tugas induk secara implisit mencegah tugas anak menempel padanya jika dibuat dengan memanggil metode Task.Run. Contoh berikut mengilustrasikan langkah-langkah ini: Ini identik dengan contoh sebelumnya, kecuali bahwa tugas induk dibuat dengan memanggil metode Task.Run(Action), bukan metode TaskFactory.StartNew(Action). Karena tugas turunan tidak dapat dilampirkan ke induknya, output dari contoh tidak dapat diprediksi. Karena opsi pembuatan tugas default untuk kelebihan beban Task.Run termasuk TaskCreationOptions.DenyChildAttach, contoh ini secara fungsional setara dengan contoh pertama di bagian "Tugas turunan yang dilepas".
using System;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
var parent = Task.Run(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays output like the following:
// Parent task executing.
// Parent has completed.
// Attached child starting.
Imports System.Threading
Imports System.Threading.Tasks
Module Example
Public Sub Main()
Dim parent = Task.Run(Sub()
Console.WriteLine("Parent task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child completing.")
End Sub, TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task executing.
' Parent has completed.
' Attached child starting.
Pengecualian dalam tugas turunan
Jika tugas turunan yang dilepas melempar pengecualian, pengecualian tersebut harus diamati atau ditangani langsung dalam tugas induk sama seperti tugas yang tidak berlapis. Jika tugas turunan yang dilampirkan melemparkan pengecualian, pengecualian secara otomatis disebarluaskan ke tugas induk dan kembali ke utas yang menunggu atau mencoba mengakses properti tugas Task<TResult>.Result. Oleh karena itu, dengan menggunakan tugas turunan terlampir, Anda dapat menangani semua pengecualian hanya pada satu titik dalam panggilan ke Task.Wait pada utas panggilan. Untuk informasi selengkapnya, lihat Penanganan Pengecualian.
Pembatalan dan tugas turunan
Pembatalan tugas bersifat kooperatif. Artinya, agar dapat dibatalkan, setiap tugas turunan yang dilampirkan atau dilepas harus memantau status token pembatalan. Jika Anda ingin membatalkan induk dan semua turunannya dengan menggunakan satu permintaan pembatalan, Anda cukup meneruskan token yang sama seagai argumen ke semua tugas dan menyediakan di setiap tugas logika untuk merespons permintaan tersebut di setiap tugas. Untuk informasi selengkapnya, lihat Pembatalan Tugas dan Cara: Membatalkan Tugas dan Turunannya.
Ketika induk membatalkan
Jika induk membatalkan dirinya sendiri sebelum tugas turunannya dimulai, maka tugas turunan tidak pernah akan dimulai. Jika induk membatalkan dirinya sendiri setelah tugas turunannya dimulai, maka tugas turunan berjalan hingga selesai kecuali memiliki logika pembatalannya sendiri. Untuk informasi selengkapnya, lihat Pembatalan Tugas.
Ketika tugas turunan yang dilepas batal
Jika tugas turunan yang dilepaskan membatalkan dirinya sendiri dengan menggunakan token yang sama yang diteruskan ke induk, dan induk tidak menunggu tugas anak, maka tidak akan ada pengecualian yang disebarkan, karena pengecualian diperlakukan sebagai pembatalan kerja sama lunak. Perilaku ini sama dengan tugas tingkat atas apa pun.
Ketika tugas turunan terlampir batal
Ketika tugas anak yang dilampirkan membatalkan dirinya sendiri dengan menggunakan token yang sama yang diteruskan ke tugas induknya, maka TaskCanceledException disebarluaskan ke utas gabungan di dalam AggregateException. Anda harus menunggu tugas induk sehingga Anda dapat menangani semua pengecualian jinak selain semua pengecualian yang salah yang disebarluaskan melalui grafik tugas turunan terlampir.
Untuk informasi selengkapnya, lihat Penanganan Pengecualian.
Mencegah tugas anak melampirkan ke induknya
Pengecualian yang tidak tertangani yang dilemparkan oleh tugas turunan disebarluaskan ke tugas induk. Anda dapat menggunakan perilaku ini untuk mengamati semua pengecualian tugas anak dari satu tugas akar alih-alih melintas pohon tugas. Namun, penyebaran pengecualian bisa bermasalah ketika tugas induk tidak mengharapkan lampiran dari kode lain. Misalnya, pertimbangkan aplikasi yang memanggil komponen pustaka pihak ketiga dari objek Task. Jika komponen pustaka pihak ketiga juga membuat objek Task dan menentukan TaskCreationOptions.AttachedToParent untuk melampirkannya ke tugas induk, pengecualian apa pun yang tidak tertangani yang terjadi dalam tugas turunan akan disebarluaskan ke induk. Hal ini dapat menyebabkan perilaku tak terduga di aplikasi utama.
Untuk mencegah tugas turunan melampirkan ke tugas induknya, tentukan opsi TaskCreationOptions.DenyChildAttach saat Anda membuat Task induk atau objek Task<TResult>. Ketika tugas mencoba melampirkan ke induknya dan induk menentukan opsi TaskCreationOptions.DenyChildAttach, maka tugas turunan tidak akan dapat melampirkan ke induk dan akan dijalankan seolah-olah opsi TaskCreationOptions.AttachedToParent tidak ditentukan.
Anda mungkin juga ingin mencegah tugas turnan melampirkan ke induknya ketika tugas anak tidak selesai tepat waktu. Karena tugas induk tidak selesai sampai semua tugas turunan selesai, tugas turunan yang berjalan lama dapat menyebabkan keseluruhan aplikasi berkinerja buruk. Untuk contoh yang memperlihatkan cara meningkatkan performa aplikasi dengan mencegah tugas melampirkan ke tugas induknya, lihat Cara: Mencegah Tugas Turunan Melampirkan ke Induknya.