Bagikan melalui


Penanganan Pengecualian (Task Parallel Library)

Pengecualian yang tidak tertangani yang dilemparkan oleh kode pengguna yang berjalan di dalam tugas disebarkan kembali ke utas panggilan, kecuali dalam skenario tertentu yang dijelaskan nanti dalam topik ini. Pengecualian dilemparkan saat Anda menggunakan salah satu metode statis atau instans Task.Wait, dan Anda mengelolanya dengan menempatkan panggilan tersebut dalam pernyataan try/catch. Jika sebuah tugas adalah induk dari tugas-tugas anak yang terlampir, atau jika Anda menunggu beberapa tugas, dapat terjadi beberapa pengecualian.

Untuk menyebarluaskan semua pengecualian kembali ke utas panggilan, infrastruktur Tugas membungkusnya dalam AggregateException instans. Pengecualian AggregateException memiliki properti InnerExceptions yang dapat dienumerasi untuk memeriksa semua pengecualian asli yang dilemparkan, dan menangani (atau tidak menangani) masing-masing satu per satu. Anda juga dapat menangani pengecualian orisinal dengan menggunakan metode AggregateException.Handle.

Bahkan jika hanya satu pengecualian yang dilemparkan, pengecualian tersebut masih dibungkus dalam pengecualian AggregateException, seperti yang ditunjukkan contoh berikut.


public static partial class Program
{
    public static void HandleThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}
// The example displays the following output:
//        This exception is expected!
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                ' Handle the custom exception.
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                    ' Rethrow any other exception.
                Else
                    Throw ex
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!

Anda dapat menghindari pengecualian yang tidak tertangani dengan hanya menangkap AggregateException dan tidak mengamati pengecualian dalamnya. Namun, kami sarankan Anda tidak melakukan ini karena seolah-olah menangkap tipe dasar Exception dalam skenario non-paralel. Menangkap pengecualian tanpa mengambil tindakan tertentu untuk memulihkan diri darinya dapat meninggalkan program Anda dalam keadaan yang tidak jelas.

Jika Anda tidak ingin memanggil Task.Wait metode untuk menunggu penyelesaian tugas, Anda juga dapat mengambil AggregateException pengecualian dari properti tugas Exception , seperti yang ditunjukkan contoh berikut. Untuk informasi selengkapnya, lihat bagian Mengamati pengecualian dengan menggunakan properti Task.Exception dalam topik ini.


public static partial class Program
{
    public static void HandleFour()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        while (!task.IsCompleted) { }

        if (task.Status == TaskStatus.Faulted)
        {
            foreach (var ex in task.Exception?.InnerExceptions ?? new(Array.Empty<Exception>()))
            {
                // Handle the custom exception.
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                // Rethrow any other exception.
                else
                {
                    throw ex;
                }
            }
        }
    }
}
// The example displays the following output:
//        This exception is expected!
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        While Not task1.IsCompleted
        End While

        If task1.Status = TaskStatus.Faulted Then
            For Each ex In task1.Exception.InnerExceptions
                ' Handle the custom exception.
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                    ' Rethrow any other exception.
                Else
                    Throw ex
                End If
            Next
        End If
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!

Perhatian

Contoh kode sebelumnya menyertakan perulangan while yang memantau properti tugas Task.IsCompleted untuk menentukan kapan tugas telah selesai. Ini tidak boleh dilakukan dalam kode produksi karena sangat tidak efisien.

Jika Anda tidak menunggu tugas yang menghasilkan pengecualian, atau mengakses properti Exception-nya, pengecualian dieskalasikan sesuai dengan kebijakan pengecualian .NET saat tugas dihapus oleh pengumpul sampah.

Ketika pengecualian diizinkan untuk menggelegak kembali ke utas penggabungan, ada kemungkinan tugas dapat terus memproses beberapa item setelah pengecualian dinaikkan.

Nota

Ketika "Just My Code" diaktifkan, Visual Studio dalam beberapa kasus akan berhenti pada baris yang melemparkan pengecualian dan menampilkan pesan kesalahan yang mengatakan "pengecualian tidak ditangani oleh kode pengguna." Kesalahan ini tidak berbahaya. Anda dapat menekan F5 untuk melanjutkan dan melihat perilaku penanganan pengecualian yang ditunjukkan dalam contoh-contoh ini. Untuk mencegah Visual Studio menghentikan eksekusi saat kesalahan pertama terjadi, cukup hilangkan centang pada kotak Aktifkan Hanya Kode Saya di bawah Alat, Opsi, Penelusuran Kesalahan, Umum.

Tugas turunan yang terlampir dan AggregateException bertingkat

Jika tugas memiliki tugas turunan terlampir yang melemparkan pengecualian, pengecualian tersebut dibungkus dalam AggregateException sebelum disebarkan ke tugas induk, yang membungkus pengecualian ini dalam AggregateException-nya sendiri sebelum menyebarkannya kembali ke utas pemanggil. Dalam kasus seperti itu, properti InnerExceptions dari pengecualian AggregateException yang tertangkap pada metode Task.Wait, WaitAny, atau WaitAll berisi satu atau beberapa instans AggregateException, bukan pengecualian asli yang menyebabkan kesalahan. Untuk menghindari harus melakukan iterasi atas pengecualian berlapis AggregateException , Anda dapat menggunakan Flatten metode untuk menghapus semua pengecualian berlapis AggregateException , sehingga AggregateException.InnerExceptions properti berisi pengecualian asli. Dalam contoh berikut, instance berlapis AggregateException diratakan dan ditangani hanya dalam satu loop.


public static partial class Program
{
    public static void FlattenTwo()
    {
        var task = Task.Factory.StartNew(() =>
        {
            var child = Task.Factory.StartNew(() =>
            {
                var grandChild = Task.Factory.StartNew(() =>
                {
                    // This exception is nested inside three AggregateExceptions.
                    throw new CustomException("Attached child2 faulted.");
                }, TaskCreationOptions.AttachedToParent);

                // This exception is nested inside two AggregateExceptions.
                throw new CustomException("Attached child1 faulted.");
            }, TaskCreationOptions.AttachedToParent);
        });

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.Flatten().InnerExceptions)
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                else
                {
                    throw;
                }
            }
        }
    }
}
// The example displays the following output:
//    Attached child1 faulted.
//    Attached child2 faulted.
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Factory.StartNew(Sub()
                                              Dim child1 = Task.Factory.StartNew(Sub()
                                                                                     Dim child2 = Task.Factory.StartNew(Sub()
                                                                                                                            Throw New CustomException("Attached child2 faulted.")
                                                                                                                        End Sub,
                                                                                                                        TaskCreationOptions.AttachedToParent)
                                                                                     Throw New CustomException("Attached child1 faulted.")
                                                                                 End Sub,
                                                                                 TaskCreationOptions.AttachedToParent)
                                          End Sub)

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.Flatten().InnerExceptions
                If TypeOf ex Is CustomException Then
                    Console.WriteLine(ex.Message)
                Else
                    Throw
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       Attached child1 faulted.
'       Attached child2 faulted.

Anda juga dapat menggunakan metode AggregateException.Flatten untuk melempar kembali pengecualian dalam dari beberapa instans AggregateException yang dilemparkan oleh beberapa pekerjaan dalam satu instans AggregateException, seperti yang diperlihatkan dalam contoh berikut.

public static partial class Program
{
    public static void TaskExceptionTwo()
    {
        try
        {
            ExecuteTasks();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.InnerExceptions)
            {
                Console.WriteLine($"{e.GetType().Name}:\n   {e.Message}");
            }
        }
    }

    static void ExecuteTasks()
    {
        // Assume this is a user-entered String.
        string path = @"C:\";
        List<Task> tasks = new();

        tasks.Add(Task.Run(() =>
        {
            // This should throw an UnauthorizedAccessException.
            return Directory.GetFiles(
                path, "*.txt",
                SearchOption.AllDirectories);
        }));

        tasks.Add(Task.Run(() =>
        {
            if (path == @"C:\")
            {
                throw new ArgumentException(
                    "The system root is not a valid path.");
            }
            return new string[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
        }));

        tasks.Add(Task.Run(() =>
        {
            throw new NotImplementedException(
                "This operation has not been implemented.");
        }));

        try
        {
            Task.WaitAll(tasks.ToArray());
        }
        catch (AggregateException ae)
        {
            throw ae.Flatten();
        }
    }
}
// The example displays the following output:
//       UnauthorizedAccessException:
//          Access to the path 'C:\Documents and Settings' is denied.
//       ArgumentException:
//          The system root is not a valid path.
//       NotImplementedException:
//          This operation has not been implemented.
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Try
            ExecuteTasks()
        Catch ae As AggregateException
            For Each e In ae.InnerExceptions
                Console.WriteLine("{0}:{2}   {1}", e.GetType().Name, e.Message,
                                  vbCrLf)
            Next
        End Try
    End Sub

    Sub ExecuteTasks()
        ' Assume this is a user-entered String.
        Dim path = "C:\"
        Dim tasks As New List(Of Task)

        tasks.Add(Task.Run(Function()
                               ' This should throw an UnauthorizedAccessException.
                               Return Directory.GetFiles(path, "*.txt",
                                                         SearchOption.AllDirectories)
                           End Function))

        tasks.Add(Task.Run(Function()
                               If path = "C:\" Then
                                   Throw New ArgumentException("The system root is not a valid path.")
                               End If
                               Return {".txt", ".dll", ".exe", ".bin", ".dat"}
                           End Function))

        tasks.Add(Task.Run(Sub()
                               Throw New NotImplementedException("This operation has not been implemented.")
                           End Sub))

        Try
            Task.WaitAll(tasks.ToArray)
        Catch ae As AggregateException
            Throw ae.Flatten()
        End Try
    End Sub
End Module
' The example displays the following output:
'       UnauthorizedAccessException:
'          Access to the path 'C:\Documents and Settings' is denied.
'       ArgumentException:
'          The system root is not a valid path.
'       NotImplementedException:
'          This operation has not been implemented.

Pengecualian dari tugas anak yang dilepas

Secara default, tugas anak dibuat sebagai dilepaskan. Pengecualian yang dilemparkan dari tugas yang dilepaskan harus ditangani atau dilemparkan kembali dalam tugas induk langsung; mereka tidak disebarkan kembali ke utas pemanggil dengan cara yang sama seperti tugas anak terlampir yang disebarkan kembali. Induk paling atas dapat secara manual menumbuhkan kembali pengecualian dari anak yang terlepas untuk menyebabkannya dibungkus dan disebarkan AggregateException kembali ke utas panggilan.


public static partial class Program
{
    public static void DetachedTwo()
    {
        var task = Task.Run(() =>
        {
            var nestedTask = Task.Run(
                () => throw new CustomException("Detached child task faulted."));

            // Here the exception will be escalated back to the calling thread.
            // We could use try/catch here to prevent that.
            nestedTask.Wait();
        });

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            foreach (var e in ae.Flatten().InnerExceptions)
            {
                if (e is CustomException)
                {
                    Console.WriteLine(e.Message);
                }
            }
        }
    }
}
// The example displays the following output:
//    Detached child task faulted.
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub()
                                 Dim nestedTask1 = Task.Run(Sub()
                                                                Throw New CustomException("Detached child task faulted.")
                                                            End Sub)
                                 ' Here the exception will be escalated back to joining thread.
                                 ' We could use try/catch here to prevent that.
                                 nestedTask1.Wait()
                             End Sub)

        Try
            task1.Wait()
        Catch ae As AggregateException
            For Each ex In ae.Flatten().InnerExceptions
                If TypeOf ex Is CustomException Then
                    ' Recover from the exception. Here we just
                    ' print the message for demonstration purposes.
                    Console.WriteLine(ex.Message)
                End If
            Next
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       Detached child task faulted.

Bahkan jika Anda menggunakan kelanjutan untuk mengamati pengecualian dalam tugas anak, pengecualian masih harus diamati oleh tugas induk.

Pengecualian yang menunjukkan pembatalan kerjasama

Ketika kode pengguna dalam tugas merespons permintaan pembatalan, prosedur yang OperationCanceledException benar adalah melemparkan passing dalam token pembatalan tempat permintaan dikomunikasikan. Sebelum mencoba menyebarkan pengecualian, instans tugas membandingkan token dalam pengecualian dengan token yang diteruskan padanya ketika dibuat. Jika mereka sama, tugas tersebut menyebarkan TaskCanceledException yang dibungkus dalam AggregateException, dan hal ini dapat terlihat ketika pengecualian internal diperiksa. Namun, jika utas panggilan tidak menunggu tugas, pengecualian khusus ini tidak akan disebarluaskan. Untuk informasi selengkapnya, lihat Pembatalan Tugas.

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
    CancellationToken ct = token;
    while (someCondition)
    {
        // Do some work...
        Thread.SpinWait(50_000);
        ct.ThrowIfCancellationRequested();
    }
},
token);

// No waiting required.
tokenSource.Dispose();
Dim someCondition As Boolean = True
Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token

Dim task1 = Task.Factory.StartNew(Sub()
                                      Dim ct As CancellationToken = token
                                      While someCondition = True
                                          ' Do some work...
                                          Thread.SpinWait(500000)
                                          ct.ThrowIfCancellationRequested()
                                      End While
                                  End Sub,
                                  token)

Menggunakan metode handle untuk memfilter pengecualian internal

Anda dapat menggunakan AggregateException.Handle metode untuk memfilter pengecualian yang dapat Anda perlakukan sebagai "ditangani" tanpa menggunakan logika lebih lanjut. Dalam delegasi pengguna yang disediakan ke AggregateException.Handle(Func<Exception,Boolean>) metode, Anda dapat memeriksa tipe pengecualian, propertinya Message, atau informasi lain tentangnya yang memungkinkan Anda menentukan apakah pengecualian tersebut jinak. Pengecualian apa pun yang delegasinya mengembalikan false akan dilemparkan kembali dalam instans baru AggregateException segera setelah metode AggregateException.Handle kembali.

Contoh berikut secara fungsional setara dengan contoh pertama dalam topik ini, yang memeriksa setiap pengecualian dalam AggregateException.InnerExceptions koleksi. Sebaliknya, penangan pengecualian ini memanggil metode objek AggregateException.Handle untuk setiap pengecualian, dan hanya melempar kembali pengecualian yang bukan instans dari CustomException.


public static partial class Program
{
    public static void HandleMethodThree()
    {
        var task = Task.Run(
            () => throw new CustomException("This exception is expected!"));

        try
        {
            task.Wait();
        }
        catch (AggregateException ae)
        {
            // Call the Handle method to handle the custom exception,
            // otherwise rethrow the exception.
            ae.Handle(ex =>
            {
                if (ex is CustomException)
                {
                    Console.WriteLine(ex.Message);
                }
                return ex is CustomException;
            });
        }
    }
}
// The example displays the following output:
//        This exception is expected!
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

        Try
            task1.Wait()
        Catch ae As AggregateException
            ' Call the Handle method to handle the custom exception,
            ' otherwise rethrow the exception.
            ae.Handle(Function(e)
                          If TypeOf e Is CustomException Then
                              Console.WriteLine(e.Message)
                          End If
                          Return TypeOf e Is CustomException
                      End Function)
        End Try
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays the following output:
'       This exception is expected!

Berikut ini adalah contoh yang lebih lengkap, yang menggunakan metode AggregateException.Handle untuk memberikan penanganan khusus untuk pengecualian UnauthorizedAccessException saat mengiterasi file.

public static partial class Program
{
    public static void TaskException()
    {
        // This should throw an UnauthorizedAccessException.
        try
        {
            if (GetAllFiles(@"C:\") is { Length: > 0 } files)
            {
                foreach (var file in files)
                {
                    Console.WriteLine(file);
                }
            }
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
            {
                Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
            }
        }
        Console.WriteLine();

        // This should throw an ArgumentException.
        try
        {
            foreach (var s in GetAllFiles(""))
            {
                Console.WriteLine(s);
            }
        }
        catch (AggregateException ae)
        {
            foreach (var ex in ae.InnerExceptions)
                Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }
    }

    static string[] GetAllFiles(string path)
    {
        var task1 =
            Task.Run(() => Directory.GetFiles(
                path, "*.txt",
                SearchOption.AllDirectories));

        try
        {
            return task1.Result;
        }
        catch (AggregateException ae)
        {
            ae.Handle(x =>
            {
                // Handle an UnauthorizedAccessException
                if (x is UnauthorizedAccessException)
                {
                    Console.WriteLine(
                        "You do not have permission to access all folders in this path.");
                    Console.WriteLine(
                        "See your network administrator or try another path.");
                }
                return x is UnauthorizedAccessException;
            });
            return Array.Empty<string>();
        }
    }
}
// The example displays the following output:
//       You do not have permission to access all folders in this path.
//       See your network administrator or try another path.
//
//       ArgumentException: The path is not of a legal form.
Imports System.IO
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        ' This should throw an UnauthorizedAccessException.
        Try
            Dim files = GetAllFiles("C:\")
            If files IsNot Nothing Then
                For Each file In files
                    Console.WriteLine(file)
                Next
            End If
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
            Next
        End Try
        Console.WriteLine()

        ' This should throw an ArgumentException.
        Try
            For Each s In GetAllFiles("")
                Console.WriteLine(s)
            Next
        Catch ae As AggregateException
            For Each ex In ae.InnerExceptions
                Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
            Next
        End Try
        Console.WriteLine()
    End Sub

    Function GetAllFiles(ByVal path As String) As String()
        Dim task1 = Task.Run(Function()
                                 Return Directory.GetFiles(path, "*.txt",
                                                           SearchOption.AllDirectories)
                             End Function)
        Try
            Return task1.Result
        Catch ae As AggregateException
            ae.Handle(Function(x)
                          ' Handle an UnauthorizedAccessException
                          If TypeOf x Is UnauthorizedAccessException Then
                              Console.WriteLine("You do not have permission to access all folders in this path.")
                              Console.WriteLine("See your network administrator or try another path.")
                          End If
                          Return TypeOf x Is UnauthorizedAccessException
                      End Function)
        End Try
        Return Array.Empty(Of String)()
    End Function
End Module
' The example displays the following output:
'       You do not have permission to access all folders in this path.
'       See your network administrator or try another path.
'
'       ArgumentException: The path is not of a legal form.

Mengamati pengecualian dengan menggunakan properti Task.Exception

Jika tugas selesai dalam TaskStatus.Faulted status, propertinya Exception dapat diperiksa untuk menemukan pengecualian spesifik mana yang menyebabkan kesalahan. Salah satu cara terbaik untuk mengamati properti Exception adalah dengan menggunakan kelanjutan yang hanya dijalankan apabila tugas sebelumnya mengalami kesalahan, seperti yang ditunjukkan dalam contoh berikut.


public static partial class Program
{
    public static void ExceptionPropagationTwo()
    {
        _ = Task.Run(
            () => throw new CustomException("task1 faulted."))
            .ContinueWith(_ =>
            {
                if (_.Exception?.InnerException is { } inner)
                {
                    Console.WriteLine($"{inner.GetType().Name}: {inner.Message}");
                }
            }, 
            TaskContinuationOptions.OnlyOnFaulted);
        
        Thread.Sleep(500);
    }
}
// The example displays output like the following:
//        CustomException: task1 faulted.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
    Public Sub Main()
        Dim task1 = Task.Factory.StartNew(Sub()
                                              Throw New CustomException("task1 faulted.")
                                          End Sub).
                    ContinueWith(Sub(t)
                                     Console.WriteLine("{0}: {1}",
                                                     t.Exception.InnerException.GetType().Name,
                                                     t.Exception.InnerException.Message)
                                 End Sub, TaskContinuationOptions.OnlyOnFaulted)

        Thread.Sleep(500)
    End Sub
End Module

Class CustomException : Inherits Exception
    Public Sub New(s As String)
        MyBase.New(s)
    End Sub
End Class
' The example displays output like the following:
'       CustomException: task1 faulted.

Dalam aplikasi yang bermakna, delegasi kelanjutan dapat mencatat informasi terperinci tentang pengecualian dan mungkin menelurkan tugas baru untuk pulih dari pengecualian. Jika tugas mengalami kesalahan, pernyataan berikut melemparkan pengecualian:

  • await task
  • task.Wait()
  • task.Result
  • task.GetAwaiter().GetResult()

Gunakan pernyataan try-catch untuk menangani dan mengamati pengecualian yang dilemparkan. Atau, amati pengecualian dengan mengakses sifat Task.Exception.

Penting

AggregateException tidak dapat ditangkap secara eksplisit saat menggunakan ekspresi berikut:

  • await task
  • task.GetAwaiter().GetResult()

Peristiwa UnobservedTaskException

Dalam beberapa skenario, seperti ketika menghosting plug-in yang tidak tepercaya, pengecualian jinak mungkin umum, dan mungkin terlalu sulit untuk mengamati semuanya secara manual. Dalam kasus ini, Anda dapat menangani acara TaskScheduler.UnobservedTaskException. Instans System.Threading.Tasks.UnobservedTaskExceptionEventArgs yang diteruskan ke handler Anda dapat digunakan untuk mencegah pengecualian yang tidak teramati agar tidak dipropagasi kembali ke utas penggabung.

Lihat juga