C#事件行动vs Func<;任务>;抛出异常

C#事件行动vs Func<;任务>;抛出异常,c#,events,async-await,C#,Events,Async Await,有了下一段代码,为什么事件的以下签名行为会有所不同 这个抛出异常: public event Action SomeEvent; public event Func<Task> SomeEvent; 此不会引发异常: public event Action SomeEvent; public event Func<Task> SomeEvent; 公共事件函数SomeEvent; 这是控制台应用程序的代码。使用动作变量声明事件会停

有了下一段代码,为什么事件的以下签名行为会有所不同

这个抛出异常:

    public event Action SomeEvent;
    public event Func<Task> SomeEvent;
不会引发异常:

    public event Action SomeEvent;
    public event Func<Task> SomeEvent;
公共事件函数SomeEvent;
这是控制台应用程序的代码。使用动作变量声明事件会停止应用程序并显示堆栈跟踪

internal static class Program
{
    private static void Main()
    {
        var watcher = new Watcher();
        watcher.Context.RaiseSomeEvent();

        Console.ReadKey();
    }
}

internal class Watcher
{
    public Context Context { get; } = new Context();

    public Watcher()
    {
        Context.SomeEvent += async () =>
        {
            Console.WriteLine($"Sleeping, current thread {Thread.CurrentThread.ManagedThreadId}");
            await Task.Run(() => Thread.Sleep(1000));
            Console.WriteLine($"Slept, current thread {Thread.CurrentThread.ManagedThreadId}");
            throw new Exception();
        };
    }
}

internal class Context
{
    public event Action SomeEvent;
    //public event Func<Task> SomeEvent;

    public void RaiseSomeEvent()
    {
        SomeEvent?.Invoke();
    }
}
内部静态类程序
{
私有静态void Main()
{
var watcher=新的watcher();
watcher.Context.RaiseSomeEvent();
Console.ReadKey();
}
}
内部类监视程序
{
公共上下文{get;}=新上下文();
公众观察者()
{
Context.SomeEvent+=async()=>
{
WriteLine($“休眠,当前线程{thread.CurrentThread.ManagedThreadId}”);
等待任务。运行(()=>Thread.Sleep(1000));
WriteLine($“睡眠,当前线程{thread.CurrentThread.ManagedThreadId}”);
抛出新异常();
};
}
}
内部类上下文
{
公共事件行动;
//公共活动职能;
公共无效RaiseSomeEvent()
{
SomeEvent?.Invoke();
}
}
我读了一些文章,例如,但没有找到任何特定于事件的内容。

事件正在抛出,但由于它被抛出到一个单独的线程中,并且主线程中没有等待任何内容,因此未观察到异常

要解决此问题以便观察到异常,您必须更改代码以等待,如下所示:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    internal static class Program
    {
        private static async Task Main()
        {
            var watcher = new Watcher();
            await watcher.Context.RaiseSomeEvent();

            Console.ReadKey();
        }
    }

    internal class Watcher
    {
        public Context Context { get; } = new Context();

        public Watcher()
        {
            Context.SomeEvent += async () =>
            {
                Console.WriteLine($"Sleeping, current thread {Thread.CurrentThread.ManagedThreadId}");
                await Task.Run(() => Thread.Sleep(1000));
                Console.WriteLine($"Slept, current thread {Thread.CurrentThread.ManagedThreadId}");
                throw new Exception();
            };
        }
    }

    internal class Context
    {
        //public event Action SomeEvent;
        public event Func<Task> SomeEvent;

        public async Task RaiseSomeEvent()
        {
            await SomeEvent?.Invoke();
        }
    }
}
使用系统;
使用系统线程;
使用System.Threading.Tasks;
名称空间演示
{
内部静态类程序
{
专用静态异步任务Main()
{
var watcher=新的watcher();
wait watcher.Context.RaiseSomeEvent();
Console.ReadKey();
}
}
内部类监视程序
{
公共上下文{get;}=新上下文();
公众观察者()
{
Context.SomeEvent+=async()=>
{
WriteLine($“休眠,当前线程{thread.CurrentThread.ManagedThreadId}”);
等待任务。运行(()=>Thread.Sleep(1000));
WriteLine($“睡眠,当前线程{thread.CurrentThread.ManagedThreadId}”);
抛出新异常();
};
}
}
内部类上下文
{
//公共事件行动;
公共活动职能;
公共异步任务RaiseSomeEvent()
{
等待某个事件?.Invoke();
}
}
}

当您使用
Action
时,您正在将lambda表达式转换为
async void
,而
Func
使其成为
async Task

异步void方法具有不同的错误处理语义。当 异常从异步任务或异步任务方法中抛出 异常被捕获并放置在任务对象上。使用异步void 方法,则没有任务对象,因此从 异步void方法将直接在 SynchronizationContext,当异步void方法 开始

因此,无论使用何种委托类型,都会引发异常,但是,
Func
版本会将异常放置在
任务上,而
Action
会直接引发异常

wait
ing使
Task
打开异常并引发异常:

public event Func<Task> SomeEvent;

public async Task RaiseSomeEvent()
{
    await SomeEvent?.Invoke();
}
公共事件函数SomeEvent;
公共异步任务RaiseSomeEvent()
{
等待某个事件?.Invoke();
}

这是事件lambda为
async
的结果。相关的正确实现异步事件是很棘手的,因为您必须处理事件有多个订阅者的情况。您必须调用,然后将委托强制转换为特定类型的异步委托,然后调用它们以获取任务,最后决定如何等待它们(顺序或并发)。在这里看一个例子:我忽略了一个事实,即我可以等待内置委托调用方法
wait SomeEvent?.Invoke()
。不知道为什么我认为它只有一个签名并且返回无效。所有的都是有效的分数。我只将前面的响应标记为answer,因为它引用了
wait SomeEvent?.Invoke()
(我忽略了这一点,因此产生了混淆),并显式地引用了主线程上的返回。