C# 从异步事件处理程序捕获异常
我有一个“后台作业”类,它异步运行作业,并在取得进展时引发事件。如果此事件的事件处理程序引发异常,则调用方法永远不会捕获该异常 如果我将事件处理程序切换为非异步。问题消失了。但是,我必须将所有来自事件处理程序的异步调用转换为阻塞调用,我不希望这样做 是否有任何方法可以捕获从异步事件处理程序引发的异常C# 从异步事件处理程序捕获异常,c#,async-await,C#,Async Await,我有一个“后台作业”类,它异步运行作业,并在取得进展时引发事件。如果此事件的事件处理程序引发异常,则调用方法永远不会捕获该异常 如果我将事件处理程序切换为非异步。问题消失了。但是,我必须将所有来自事件处理程序的异步调用转换为阻塞调用,我不希望这样做 是否有任何方法可以捕获从异步事件处理程序引发的异常 using System; using System.Threading.Tasks; namespace AsyncEventHandlingIssue { public class B
using System;
using System.Threading.Tasks;
namespace AsyncEventHandlingIssue
{
public class BackgroundJob
{
public int Progress { get; private set; }
public event EventHandler ProgressUpdated;
public async Task Start()
{
for (var i = 0; i < 100; i++)
{
await Task.Delay(1000);
Progress++;
ProgressUpdated?.Invoke(this, EventArgs.Empty);
}
}
}
public class Program
{
static async Task MainAsync()
{
var job = new BackgroundJob();
job.ProgressUpdated += Job_ProgressUpdated;
try
{
await job.Start();
}
catch(Exception ex)
{
Console.WriteLine($"The job failed with an error: {ex}");
}
}
private static async void Job_ProgressUpdated(object sender, EventArgs e)
{
var job = (BackgroundJob)sender;
await Task.Delay(100); // just an example - my real code needs to call async methods.
Console.WriteLine($"The Job is at {job.Progress}%.");
if (job.Progress == 5)
throw new Exception("Something went wrong!");
}
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
Console.WriteLine("Reached the end of the program.");
Console.ReadKey();
}
}
}
使用系统;
使用System.Threading.Tasks;
命名空间AsyncEventHandlingIssue
{
公开课背景工作
{
public int Progress{get;private set;}
公共事件处理程序更新;
公共异步任务启动()
{
对于(变量i=0;i<100;i++)
{
等待任务。延迟(1000);
进步++;
ProgressUpdated?.Invoke(此为EventArgs.Empty);
}
}
}
公共课程
{
静态异步任务mainsync()
{
var job=new BackgroundJob();
job.ProgressUpdated+=job\u ProgressUpdated;
尝试
{
等待作业。开始();
}
捕获(例外情况除外)
{
WriteLine($“作业失败,出现错误:{ex}”);
}
}
私有静态异步无效作业\u ProgressUpdated(对象发送方,事件参数e)
{
var job=(BackgroundJob)发送方;
wait Task.Delay(100);//只是一个例子-我的实际代码需要调用异步方法。
WriteLine($“作业位于{Job.Progress}%.”);
如果(作业进度==5)
抛出新异常(“出错了!”);
}
静态void Main(字符串[]参数)
{
MainAsync().GetAwaiter().GetResult();
WriteLine(“到达程序末尾”);
Console.ReadKey();
}
}
}
一个异步void
方法将在其捕获的上下文中抛出它的异常。您的try catch
应该在异步
方法中。此外,您所做的工作听起来非常像旧的BackgroundWorker
,不要重新发明轮子,而且已经有了更好的选项来运行报告进度的异步
作业。查看async
进度更新。下面是一个利用async wait
和async
进度更新的简单示例:
public class BackgroundJob {
public async Task Start(IProgress<int> progress) {
for (var i = 0; i < 100; i++) {
await Task.Delay(1000);
progress.Report(i);
//the method executing the job should determine something is wrong
if (i == 5)
throw new Exception("Something went wrong!");
}
}
}
public class Program {
static async Task MainAsync() {
var job = new BackgroundJob();
var progress = new Progress<int>(Job_ProgressUpdated);
try {
await job.Start(progress);
} catch (Exception ex) {
//now your exception is caught
Console.WriteLine($"The job failed with an error: {ex}");
}
}
private static async void Job_ProgressUpdated(int progress) {
await Task.Delay(100); // just an example - my real code needs to call async methods.
Console.WriteLine($"The Job is at {progress}%.");
//***
//a progress update should not determine if something went wrong
//***
//if (progress == 5)
//throw new Exception("Something went wrong!");
}
static void Main(string[] args) {
MainAsync().GetAwaiter().GetResult();
Console.WriteLine("Reached the end of the program.");
Console.ReadKey();
}
}
我只想回答你的评论,因为你的问题已经基本上涵盖了 如果异步void事件处理程序中的异常永远无法捕获,那么它们不应该总是包装在一个覆盖的try/catch中以避免应用程序崩溃吗 每当在
async void
方法中抛出异常时,它都会被发布到当前同步上下文中。大多数情况下,它会导致应用程序崩溃。这就是为什么无论何时只要有意义,async void
方法都应该捕获异常的原因
也就是说,有一种从async void
方法外部捕获异常的黑客方法:构建自定义同步上下文以拦截异常
public static void Test()
{
throw new Exception("Synchronous");
}
public static async void TestAsync()
{
await Task.Yield();
throw new Exception("Asynchronous");
}
public class EventSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
if (state is ExceptionDispatchInfo
&& d.Target.GetType().ReflectedType.FullName == "System.Runtime.CompilerServices.AsyncMethodBuilderCore")
{
// Caught an exception
var exceptionInfo = (ExceptionDispatchInfo)state;
Console.WriteLine("Caught asynchronous exception: " + exceptionInfo.SourceException);
return;
}
base.Post(d, state);
}
}
static void Main(string[] args)
{
SomeEvent += TestAsync;
SomeEvent += Test;
var previousSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(new EventSynchronizationContext());
SomeEvent();
}
catch (Exception ex)
{
Console.WriteLine("Caught synchronous exception: " + ex);
}
finally
{
SynchronizationContext.SetSynchronizationContext(previousSynchronizationContext);
}
Console.ReadLine();
}
我再说一遍:我只是为好奇的人张贴这篇文章。这依赖于未记录的内部机制,这些机制可能会在框架的任何更新时中断,并且不应在实际的生产代码中使用。EventHandler不会接受异常,即使使用
async void
,它们也会抛出异常exceptions@Fabio谢谢你,今天早上我有点超前了。@JSteward我的代码示例真正要问的是如何处理从异步void事件处理程序引发的异常。假设任何可能引发某些事件的非平凡事件处理程序,所有异步void事件处理程序是否都应该包装在一个包中。。。接住?我将查看您提供的TAP链接。谢谢@James您可以在事件处理程序中捕获异常,这显然没有帮助,但是如果您想用异常终止该作业,则需要取消该作业。将适当的async
进度更新和取消结合起来,您将拥有一个性能良好的流程。另一方面,如果你有很多工作要处理,那么你可能会被引导去问错误的问题。进度报告是观察者模式的实现。您永远不需要捕获事件源中的处理程序异常;事实上,您需要表明您使用的不是观察者模式,而是其他模式。例如,模板方法经常使用事件错误地实现。@StephenCleary这是一个很好的观点。我确实对你的建议感到内疚,我很确定我可以使用下面从JSteward获得的技巧来重写,以避免这个问题。现在,为了个人的好奇心,有了这些,事件处理代码是否总是可能抛出异常?如果异步void事件处理程序中的异常永远无法捕获,那么它们不应该总是包装在一个覆盖的try/catch中以避免应用程序崩溃吗?谢谢<代码>异步void模拟(顶级)UI事件处理程序;当UI事件引发异常时,它直接进入消息循环,因此async void
方法通过在SynchronizationContext
上直接引发异常来模拟该行为。因此,如果您需要在UI事件处理程序中使用try/catch,那么您将需要在async void
方法中使用try/catch。有一些变通方法可以捕获异常(比如让任务
返回事件),但重新设计是最好的方法。
public static void Test()
{
throw new Exception("Synchronous");
}
public static async void TestAsync()
{
await Task.Yield();
throw new Exception("Asynchronous");
}
public class EventSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
if (state is ExceptionDispatchInfo
&& d.Target.GetType().ReflectedType.FullName == "System.Runtime.CompilerServices.AsyncMethodBuilderCore")
{
// Caught an exception
var exceptionInfo = (ExceptionDispatchInfo)state;
Console.WriteLine("Caught asynchronous exception: " + exceptionInfo.SourceException);
return;
}
base.Post(d, state);
}
}
static void Main(string[] args)
{
SomeEvent += TestAsync;
SomeEvent += Test;
var previousSynchronizationContext = SynchronizationContext.Current;
try
{
SynchronizationContext.SetSynchronizationContext(new EventSynchronizationContext());
SomeEvent();
}
catch (Exception ex)
{
Console.WriteLine("Caught synchronous exception: " + ex);
}
finally
{
SynchronizationContext.SetSynchronizationContext(previousSynchronizationContext);
}
Console.ReadLine();
}