ContinueWith TaskContinuationOptions.OnlyOnFaulted 似乎没有捕捉到从已启动任务中抛出的异常
ContinueWith TaskContinuationOptions.OnlyOnFaulted does not seem to catch an exception thrown from a started task
我正在尝试使用 ContinueWith 和 OnlyOnFaulted 捕获从任务方法抛出的异常,如下所示。但是,当我尝试 运行 这段代码时,出现了未处理的异常。
我希望任务 运行 完成,因为我已经处理了异常。但是 Task.Wait() 运行 进入 AggregateException。
var taskAction = new Action(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
如果我像下面这样在任务方法中处理异常,一切都会如我所料地正常进行。任务运行到完成,异常被记录并且 Wait 也成功。
var taskAction = new Action(() =>
{
try
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception ex)
{
Console.WriteLine("Catching the exception in the Action catch block only");
}
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x=> Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
我的问题是:我使用 OnlyOnFaulted
是否正确,还是在任务方法本身中处理异常总是更好?即使任务 运行s 出现异常,我也希望主线程继续。另外,我想从任务方法中记录该异常。
注意:我必须等待任务方法完成才能继续(有或没有错误)。
总结一下(目前我的理解)
如果来自 Task 的异常得到处理,即如果 wait 或 await 捕获异常,则异常将传播到 continuedtask onfaulted。
即使在任务方法和 consumed\handled 中也可以捕获异常。
try
{
t.wait();
}
catch(Exception e)
{
LogError(e);
}
在上述情况下,在调用 LogError 之前,会执行与主任务的 onfaulted 关联的继续任务。
看来您的方向是正确的。我有 运行 你的代码并得到相同的结果。我的建议是直接从操作内部使用 try/catch。这将使您的代码更清晰,并允许您记录它来自哪个线程之类的东西,这在延续路径中可能会有所不同。
var taskAction = new Action(() =>
{
try{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception e)
{
Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ e);
}
});
Task t = Task.Factory.StartNew(taskAction);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
await t;
[编辑] 删除了外部异常处理程序。
Am I using the TaskContinutationOptions.OnlyOnFaulted
correctly or is it always better to
handle the exception in the task method itself? I would like the main
thread to continue even if task runs into exception.
您可以在内部或外部以任何方式处理异常。这是一个偏好问题,通常取决于用例。
请注意,您没有做的一件事是保留对您的延续的引用。您在传播异常的原始任务上使用 Task.Wait
,而不管您是否有处理它的延续。
让我困扰的一件事是您使用的是 Task.Wait
同步等待 而不是 await
异步等待。这就是 AggregationException
的原因。更重要的是,您不应该阻止异步操作,因为这会导致您陷入一个您可能不想去的兔子洞,并出现各种同步上下文问题。
我个人会做的是在 ContinueWith
中使用 await
,因为它的选项不那么冗长。另外,我会使用 Task.Run over Task.Factory.StartNew:
var task = Task.Run(() =>
{
Thread.Sleep(1000);
throw new InvalidOperationException();
}
// Do more stuff here until you want to await the task.
try
{
await task;
}
catch (InvalidOperationException ioe)
{
// Log.
}
首先,您没有正确使用 OnlyOnFaulted
。当你在一个任务上使用 ContinueWith
时你并没有真正改变那个任务,你会得到一个任务延续(在你的情况下你会忽略它)。
如果原始任务出错(即在其中抛出异常)它将保持出错状态(因此对其调用 Wait()
总是会重新抛出异常)。然而,任务失败后继续 运行 并处理异常。
这意味着在您的代码中您确实处理了异常,但您还使用 Wait()
重新抛出它。正确的代码应该是这样的:
Task originalTask = Task.Run(() => throw new Exception());
Task continuationTask = originalTask.ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
continuationTask.Wait()
// Both tasks completed. No exception rethrown
现在,正如 Yuval Itzchakov 指出的那样,您可以在任何地方处理异常,但如果可以的话,最好利用 async-await
异步等待(您不能在 Main
) 而不是阻塞 Wait()
:
try
{
await originalTask;
}
catch (Exception e)
{
// handle exception
}
最初的问题恰好 运行 它的 taskAction
在一个单独的线程上。情况可能并非总是如此。
很好的解决了问题。如果我们不想使用单独的线程怎么办?如果我们想简单地启动一个任务,运行同步它直到我们遇到 IO 或延迟,然后继续我们自己的业务。只有在最后,我们才会等待任务完成。
我们如何通过重新抛出异常来等待任务?我们将使用一个空的延续,而不是将其包装在 Task.Run()
中,当任务出于任何原因完成时,它总是会成功。
// Local function that waits a moment before throwing
async Task ThrowInAMoment()
{
await Task.Delay(1000);
Console.WriteLine("Task waited for a sec");
throw new Exception("Throwing for example");
}
// Start the task without waiting for it to complete
var t = ThrowInAMoment();
// We reach this line as soon as ThrowInAMoment() can no longer proceed synchronously
// This is as soon as it hits its "await Delay(1000)"
// Handle exceptions in the task
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
// Continue about our own business
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
// Now we want to wait for the original task to finish
// But we do not care about its exceptions, as they are already being handled
// We can use ContinueWith() to get a task that will be in the completed state regardless of HOW the original task finished (RanToCompletion, Faulted, Canceled)
await t.ContinueWith(task => {});
// Could use .Wait() instead of await if you want to wait synchronously for some reason
我正在尝试使用 ContinueWith 和 OnlyOnFaulted 捕获从任务方法抛出的异常,如下所示。但是,当我尝试 运行 这段代码时,出现了未处理的异常。
我希望任务 运行 完成,因为我已经处理了异常。但是 Task.Wait() 运行 进入 AggregateException。
var taskAction = new Action(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
如果我像下面这样在任务方法中处理异常,一切都会如我所料地正常进行。任务运行到完成,异常被记录并且 Wait 也成功。
var taskAction = new Action(() =>
{
try
{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception ex)
{
Console.WriteLine("Catching the exception in the Action catch block only");
}
});
Task t = Task.Factory.StartNew(taskAction);
t.ContinueWith(x=> Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
t.Wait();
我的问题是:我使用 OnlyOnFaulted
是否正确,还是在任务方法本身中处理异常总是更好?即使任务 运行s 出现异常,我也希望主线程继续。另外,我想从任务方法中记录该异常。
注意:我必须等待任务方法完成才能继续(有或没有错误)。
总结一下(目前我的理解)
如果来自 Task 的异常得到处理,即如果 wait 或 await 捕获异常,则异常将传播到 continuedtask onfaulted。 即使在任务方法和 consumed\handled 中也可以捕获异常。
try
{
t.wait();
}
catch(Exception e)
{
LogError(e);
}
在上述情况下,在调用 LogError 之前,会执行与主任务的 onfaulted 关联的继续任务。
看来您的方向是正确的。我有 运行 你的代码并得到相同的结果。我的建议是直接从操作内部使用 try/catch。这将使您的代码更清晰,并允许您记录它来自哪个线程之类的东西,这在延续路径中可能会有所不同。
var taskAction = new Action(() =>
{
try{
Thread.Sleep(1000);
Console.WriteLine("Task Waited for a sec");
throw (new Exception("throwing for example"));
}
catch (Exception e)
{
Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ e);
}
});
Task t = Task.Factory.StartNew(taskAction);
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
await t;
[编辑] 删除了外部异常处理程序。
Am I using the
TaskContinutationOptions.OnlyOnFaulted
correctly or is it always better to handle the exception in the task method itself? I would like the main thread to continue even if task runs into exception.
您可以在内部或外部以任何方式处理异常。这是一个偏好问题,通常取决于用例。
请注意,您没有做的一件事是保留对您的延续的引用。您在传播异常的原始任务上使用 Task.Wait
,而不管您是否有处理它的延续。
让我困扰的一件事是您使用的是 Task.Wait
同步等待 而不是 await
异步等待。这就是 AggregationException
的原因。更重要的是,您不应该阻止异步操作,因为这会导致您陷入一个您可能不想去的兔子洞,并出现各种同步上下文问题。
我个人会做的是在 ContinueWith
中使用 await
,因为它的选项不那么冗长。另外,我会使用 Task.Run over Task.Factory.StartNew:
var task = Task.Run(() =>
{
Thread.Sleep(1000);
throw new InvalidOperationException();
}
// Do more stuff here until you want to await the task.
try
{
await task;
}
catch (InvalidOperationException ioe)
{
// Log.
}
首先,您没有正确使用 OnlyOnFaulted
。当你在一个任务上使用 ContinueWith
时你并没有真正改变那个任务,你会得到一个任务延续(在你的情况下你会忽略它)。
如果原始任务出错(即在其中抛出异常)它将保持出错状态(因此对其调用 Wait()
总是会重新抛出异常)。然而,任务失败后继续 运行 并处理异常。
这意味着在您的代码中您确实处理了异常,但您还使用 Wait()
重新抛出它。正确的代码应该是这样的:
Task originalTask = Task.Run(() => throw new Exception());
Task continuationTask = originalTask.ContinueWith(t => Console.WriteLine(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
continuationTask.Wait()
// Both tasks completed. No exception rethrown
现在,正如 Yuval Itzchakov 指出的那样,您可以在任何地方处理异常,但如果可以的话,最好利用 async-await
异步等待(您不能在 Main
) 而不是阻塞 Wait()
:
try
{
await originalTask;
}
catch (Exception e)
{
// handle exception
}
最初的问题恰好 运行 它的 taskAction
在一个单独的线程上。情况可能并非总是如此。
我们如何通过重新抛出异常来等待任务?我们将使用一个空的延续,而不是将其包装在 Task.Run()
中,当任务出于任何原因完成时,它总是会成功。
// Local function that waits a moment before throwing
async Task ThrowInAMoment()
{
await Task.Delay(1000);
Console.WriteLine("Task waited for a sec");
throw new Exception("Throwing for example");
}
// Start the task without waiting for it to complete
var t = ThrowInAMoment();
// We reach this line as soon as ThrowInAMoment() can no longer proceed synchronously
// This is as soon as it hits its "await Delay(1000)"
// Handle exceptions in the task
t.ContinueWith(x => Console.WriteLine("In the on Faulted continue with code. Catched exception from the task."+ t.Exception), TaskContinuationOptions.OnlyOnFaulted);
// Continue about our own business
Console.WriteLine("Main thread waiting for 4 sec");
Thread.Sleep(4000);
Console.WriteLine("Wait of 4 secs complete..checking if task is completed?");
Console.WriteLine("Task State: " + t.Status);
// Now we want to wait for the original task to finish
// But we do not care about its exceptions, as they are already being handled
// We can use ContinueWith() to get a task that will be in the completed state regardless of HOW the original task finished (RanToCompletion, Faulted, Canceled)
await t.ContinueWith(task => {});
// Could use .Wait() instead of await if you want to wait synchronously for some reason