Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/svg/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 从异步事件处理程序捕获异常_C#_Async Await - Fatal编程技术网

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();
}