Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Neošetřené výjimky vyvolané uživatelským kódem spuštěným uvnitř úlohy se rozšíří zpět do volajícího vlákna s výjimkou určitých scénářů popsaných dále v tomto tématu. Výjimky se propagují, když použijete některou ze statických metod nebo metod instance Task.Wait, a ošetříte je tím, že volání uzavřete do příkaz try
/catch
. Pokud je úkol nadřazený připojeným podřízeným úkolům nebo pokud čekáte na více úkolů, může dojít k vyvolání několika výjimek.
Aby se všechny výjimky rozšířily zpět do volajícího vlákna, infrastruktura úloh je zabalí do AggregateException instance. Výjimka AggregateException má vlastnost InnerExceptions, kterou lze procházet pomocí enumerace, aby se prozkoumaly všechny původní výjimky, které byly vyvolány, a každá mohla být individuálně zpracována (nebo nezpracována). Původní výjimky můžete zpracovat také pomocí AggregateException.Handle metody.
I když je vyvolána pouze jediná výjimka, je stále zabalena do AggregateException výjimky, jak ukazuje následující příklad.
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!
Můžete se vyhnout neošetřené výjimce tím, že zachytíte pouze AggregateException a nebudete sledovat žádné vnitřní výjimky. Doporučujeme ale, abyste to neudělali, protože je to podobné zachycení základního typu Exception v neparalelních scénářích. Pokud zachytíte výjimku, aniž byste podnikli konkrétní kroky k zotavení z ní, může váš program zůstat v nedefinovaném stavu.
Pokud nechcete volat metodu Task.Wait aby se počkalo na dokončení úkolu, můžete také načíst AggregateException výjimku z úkolu pomocí jeho vlastnosti Exception, jak je uvedeno v následujícím příkladu. Další informace naleznete v části Pozorování výjimek pomocí vlastnosti Task.Exception v tomto tématu.
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!
Upozornění
Předchozí ukázkový kód obsahuje smyčku while
, která se dotazuje na vlastnost úkolu Task.IsCompleted , aby určila, kdy je úkol dokončen. To by nikdy nemělo být provedeno v produkčním kódu, protože je velmi neefektivní.
Pokud nečekáte na úlohu, která vyvolá výjimku, nebo přistupujete k její Exception vlastnosti, výjimka se eskaluje podle zásad výjimek .NET při uvolnění paměti úkolu.
Pokud je výjimkám umožněno probublat zpět do spojovacího vlákna, je možné, že úloha může pokračovat ve zpracování některých položek i poté, co je vyvolána výjimka.
Poznámka:
Pokud je povolená možnost „Jen můj kód“, Visual Studio se v některých případech zastaví na řádku, který vyvolá výjimku, a zobrazí chybovou zprávu „Výjimka nebyla zpracována uživatelským kódem.“ Tato chyba je neškodná. Stisknutím klávesy F5 můžete pokračovat a zobrazit chování zpracování výjimek, které je znázorněno v těchto příkladech. Pokud chcete zabránit tomu, aby se Visual Studio zastavilo při první chybě, zrušte zaškrtnutí políčka Povolit pouze můj kód v části Nástroje, Možnosti, Ladění, Obecné.
Připojené podřízené úlohy a vnořené funkce AggregateExceptions
Pokud má úkol připojenou podřízenou úlohu, která vyvolá výjimku, je tato výjimka zabalena do AggregateException před jejím předáním nadřazené úloze, která ji zabalí do svého AggregateException , než ji předá zpět do volajícího vlákna. V takových případech vlastnost InnerExceptions výjimky zachycené v AggregateException, Task.Wait nebo WaitAny metodě obsahuje jednu nebo více instancí WaitAll, nikoli původní výjimky, které způsobily chybu. Pokud se chcete vyhnout iteraci nad vnořenými AggregateException výjimkami, můžete pomocí Flatten metody odebrat všechny vnořené AggregateException výjimky, aby AggregateException.InnerExceptions vlastnost obsahovala původní výjimky. V následujícím příkladu se vnořené AggregateException instance zjednoduší a zpracovávají jen v jednom cyklu.
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.
Metodu AggregateException.Flatten můžete také použít k opětovnému rozvětvování vnitřních výjimek z více instancí vyvolaných více AggregateException úlohami v jedné AggregateException instanci, jak ukazuje následující příklad.
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.
Výjimky u oddělených podřízených úkolů
Ve výchozím nastavení se podřízené úkoly vytvářejí jako odpojené. Výjimky vyvolané odpojenými úkoly musí být zpracovávány nebo znovu vyvolány v bezprostřední nadřazené úloze; nejsou vraceny zpět do volajícího vlákna stejným způsobem, jako jsou šířeny připojené podřízené úlohy zpět. Nadřazený objekt nejvyšší úrovně může ručně znovu vyvolat výjimku z odpojené podřízené položky, aby byla zabalena do AggregateException a předána zpět do volajícího vlákna.
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.
I když použijete pokračování ke sledování výjimky v podřízené úloze, musí být výjimka stále pozorována nadřazenou úlohou.
Výjimky, které označují zrušení spolupráce
Když uživatelský kód v úloze odpoví na žádost o zrušení, je správným postupem vyvolání předávajícího OperationCanceledException tokenu zrušení, na kterém byl požadavek oznámen. Než se pokusí rozšířit výjimku, instance úlohy porovná token v výjimce s tokenem, který byl předán při jeho vytvoření. Pokud jsou stejné, úloha rozšíří TaskCanceledException, zabalené ve AggregateException, a je to vidět, když se zkoumají vnitřní výjimky. Pokud však volající vlákno nečeká na úlohu, tato konkrétní výjimka nebude propagována. Další informace naleznete v části Zrušení úkolu.
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)
Použití metody zpracování k filtrování vnitřních výjimek
Metodu AggregateException.Handle můžete použít k vyfiltrování výjimek, které můžete považovat za "zpracovávané" bez použití jakékoli další logiky. V delegátu uživatele, který je zadán metodě AggregateException.Handle(Func<Exception,Boolean>) , můžete prozkoumat typ výjimky, jeho Message vlastnost nebo jakékoli další informace o něm, které vám umožní určit, zda je neškodné. Všechny výjimky, pro které delegát vrátí false
, se znovu vrátí v nové AggregateException instanci ihned po AggregateException.Handle vrácení metody.
Následující příklad je funkčně ekvivalentní prvnímu příkladu v tomto tématu, který zkoumá každou výjimku v kolekci AggregateException.InnerExceptions . Místo toho tato obslužná rutina výjimky volá AggregateException.Handle objekt metody pro každou výjimku a pouze znovu vyvolává výjimky, které nejsou instance 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!
Následuje úplný příklad, který používá metodu AggregateException.Handle k poskytnutí speciálního zpracování výjimky UnauthorizedAccessException při vytváření výčtu souborů.
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.
Pozorování výjimek pomocí Task.Exception vlastnost
Pokud se úkol dokončí ve TaskStatus.Faulted stavu, lze jeho Exception vlastnost prozkoumat a zjistit, která konkrétní výjimka způsobila chybu. Dobrý způsob, jak pozorovat vlastnost Exception, je použít následné pokračování, které se spustí pouze v případě, že úloha předchůdce selže, jak ukazuje následující příklad.
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.
V smysluplné aplikaci by delegát pokračování mohl protokolovat podrobné informace o výjimce a případně vytvořit nové úlohy, které mohou pomoci zotavit se z výjimky. Pokud dojde k chybě úkolu, vyvolá se výjimka pomocí následujících výrazů:
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
Použijte příkaz try-catch
pro zpracování a sledování vyvolaných výjimek. Případně můžete sledovat výjimku přístupem k vlastnosti Task.Exception.
Důležité
Při použití AggregateException nelze explicitně zachytit následující výrazy:
await task
task.GetAwaiter().GetResult()
Událost UnobservedTaskException
V některých scénářích, například při hostování nedůvěryhodných modulů plug-in, můžou být běžné neškodné výjimky a může být příliš obtížné je ručně sledovat všechny. V těchto případech můžete událost TaskScheduler.UnobservedTaskException zpracovat. Instanci System.Threading.Tasks.UnobservedTaskExceptionEventArgs předanou obslužné rutině lze použít k zabránění šíření neoobslužné výjimky zpět do spojovacího vlákna.