等待超时后使用 Task.Wait 引发两次错误
这个问题似乎源于我正在使用 Task.Wait 等待超时。问题是任务超时后引发的异常被记录两次,或者在超时之前引发的错误未被记录。我已经添加了我用来更好地理解场景的代码和测试。
这个测试背后的想法是我们强制在抛出异常之前(在 3 秒)发生超时(在 2 秒)。在这种情况下,异常会发生什么?下面是结果。从未报告过“繁荣”异常。它在任务中仍然是一个未观察到的异常。
[MassUpdateEngine.cs]
// Package the collection of statements that need to be run as a Task.
// The Task can then be given a cancellation token and a timeout.
Task task = Task.Run(async () =>
{
try
{
Thread.Sleep(3000);
throw new Exception("boom");
// Checking whether the task was cancelled at each step in the task gives us finer grained control over when we should bail out.
token.ThrowIfCancellationRequested();
Guid id = SubmitPreview();
results.JobId = id;
token.ThrowIfCancellationRequested();
bool previewStatus = await GetPreviewStatus(id, token);
Logger.Log("Preview status: " + previewStatus);
token.ThrowIfCancellationRequested();
ExecuteUpdate(id);
token.ThrowIfCancellationRequested();
bool updateStatus = await GetUpdateStatus(id, token);
Logger.Log("Update status: " + updateStatus);
token.ThrowIfCancellationRequested();
string value = GetUpdateResults(id);
results.NewValue = value;
}
// It appears that awaited methods will throw exceptions on when cancelled.
catch (OperationCanceledException)
{
Logger.Log("***An operation was cancelled.***");
}
}, token);
task.ContinueWith(antecedent =>
{
//Logger.Log(antecedent.Exception.ToString());
throw new CustomException();
}, TaskContinuationOptions.OnlyOnFaulted);
[Program.cs]
try
{
MassUpdateEngine engine = new MassUpdateEngine();
// This call simulates calling the MassUpdate.Execute method that will handle preview + update all in one "synchronous" call.
//Results results = engine.Execute();
// This call simulates calling the MassUpdate.Execute method that will handle preview + update all in one "synchronous" call along with a timeout value.
// Note: PreviewProcessor and UpdateProcessor both sleep for 3 seconds each. The timeout needs to be > 6 seconds for the call to complete successfully.
int timeout = 2000;
Results results = engine.Execute(timeout);
Logger.Log("Results: " + results.NewValue);
}
catch (TimeoutException ex)
{
Logger.Log("***Timeout occurred.***");
}
catch (AggregateException ex)
{
Logger.Log("***Aggregate exception occurred.***\n" + ex.ToString());
}
catch (CustomException ex)
{
Logger.Log("A custom exception was caught and handled.\n" + ex.ToString());
}
因为从未观察到异常,因此没有适本地记录,这将不起作用。
此信息导致以下规则:
规则 #2 非常丑陋。在这种情况下,我们如何可靠地记录异常?我们可以使用 .ContinueWith/OnlyOnFaulted 来记录异常(见下文)。
task.ContinueWith(antecedent =>
{
Logger.Log(antecedent.Exception.ToString());
//throw new CustomException();
}, TaskContinuationOptions.OnlyOnFaulted);
但是,如果在等待调用超时之前发生异常,则异常将被编码回调用线程并由全局未处理异常处理程序处理(并记录),然后将传递给 .ContinueWith 任务(并记录),导致同一异常的两个错误日志条目。
这里一定有我遗漏的东西。任何帮助,将不胜感激。
最佳答案
Don’t throw from .ContinueWith. Exceptions thrown from here aren’t marshaled back to the calling thread. These exceptions remain as unobserved exceptions and are effectively eaten.
未观察到的异常的原因是因为未观察到任务(不是因为它是使用
ContinueWith
创建的)。从 ContinueWith
返回的任务只是被忽略了。我想说更合适的建议是 not use
ContinueWith
at all (有关详细信息,请参阅我的博客)。一旦你改为使用 await
,答案就更清楚了:public static async Task LogExceptions(Func<Task> func)
{
try
{
await func();
}
catch (Exception ex)
{
Logger.Log(ex.ToString());
}
}
public static async Task<T> LogExceptions<T>(Func<Task<T>> func)
{
try
{
return await func();
}
catch (Exception ex)
{
Logger.Log(ex.ToString());
}
}
async
/await
模式更清楚地说明了这里真正发生的事情:代码正在创建一个包装器任务,该任务必须用作原始任务的替代品:Task task = LogExceptions(() => Task.Run(() => ...
现在包装器任务将始终观察其内部任务的任何异常。
An exception from a Task may or may not be marshalled back to the calling thread when using Wait with a timeout. If the exception occurs BEFORE the timeout for the Wait call, then the exception gets marshalled back to the calling thread. If the exception occurs AFTER the timeout for the Wait call, then the exception remains as an unobserved exception of the task.
我会根据异常在哪里而不是“编码”或“调用线程”来考虑这一点。这只是混淆了这个问题。当一个任务发生故障时,该异常被放置在该任务上(并且该任务完成)。因此,如果在等待完成之前将异常放置在任务上,则等待由任务完成来满足,并引发异常。如果在任务完成之前等待就超时了,那么等待就不再等待了,所以它当然看不到异常,也有可能没有观察到异常。
关于等待超时后使用 Task.Wait 记录 C#/Tasks 错误两次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39452667/