我有一个“后台作业”类,它以异步方式运行作业,并在进行任何进度时引发事件。如果此事件的事件处理程序引发异常,则调用方法永远不会捕获它。
如果我将事件处理程序切换为非异步。问题消失了。但是,我必须将所有从事件处理程序调用的异步调用转换为阻塞调用,而我宁愿不必这样做!
有没有办法捕获异步事件处理程序引发的异常?
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();
}
}
}
答案 0 :(得分:2)
async void
方法会在其捕获的上下文中引发它的try catch
应该在async
方法中的异常。此外,你的行为听起来很像旧的BackgroundWorker
,不会重新发明轮子,并且已经有更好的选择来运行报告进度的async
个工作。查看async
进度更新here。这是一个利用async await
和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();
}
}
修改强>
您需要考虑从进度更新的事件处理程序或任何async
事件处理程序中抛出异常不一定会破坏您的工作。但是,您可以从事件处理程序中取消作业,并像这样捕获OperationCanceledException
:
public class BackgroundJob {
public int Progress { get; private set; }
public event EventHandler ProgressUpdated;
public async Task Start(CancellationToken token) {
for (var i = 0; i < 100; i++) {
token.ThrowIfCancellationRequested();
await Task.Delay(1000);
Progress++;
ProgressUpdated?.Invoke(this, EventArgs.Empty);
}
}
}
public class Program {
private static CancellationTokenSource cts = new CancellationTokenSource()
static async Task MainAsync() {
var job = new BackgroundJob();
job.ProgressUpdated += Job_ProgressUpdated;
try {
await job.Start(cts.Token);
} catch (OperationCanceledException 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)
cts.Cancel();
}
static void Main(string[] args) {
MainAsync().GetAwaiter().GetResult();
Console.WriteLine("Reached the end of the program.");
Console.ReadKey();
}
}
答案 1 :(得分:1)
我只是回答你的评论,因为你的问题已经被大部分覆盖了。
如果异步void事件处理程序中的异常永远不会被捕获,那么它们是否应该总是包装在一个try / catch中以避免崩溃应用程序?
只要在ds = gdal.Open("path to some file")
for band in range( ds.RasterCount ):
band += 1
nparray = ds.GetRasterBand(band).ReadAsArray()
方法中抛出异常,就会将其发布到当前同步上下文。大多数情况下,它会导致应用程序崩溃。这就是为什么,是的,async void
方法应该在有意义时捕获异常。
也就是说,有一种从async void
方法外部捕获异常的hackish方法:构建自定义同步上下文来拦截异常。
async void
我再说一遍:我只是为那些好奇的头脑发帖子。这依赖于未记录的内部机制,这些内部机制可能会在框架的任何更新时中断,并且不应在实际的生产代码中使用。