C# 如何使用显式构造的任务处理异常

C# 如何使用显式构造的任务处理异常,c#,exception-handling,async-await,task-parallel-library,C#,Exception Handling,Async Await,Task Parallel Library,我有一个项目,它以非常相似的方式执行多个操作(订阅完成事件、执行任务、取消订阅完成事件以及处理取消、超时等),因此我决定编写一个实用程序类来处理该执行。然而,我遇到了一个我不理解的场景,因此不知道如何修复 这段过于简化的代码说明了问题: class Program { static void Main(string[] args) { Do(); Console.Read(); } private static async Tas

我有一个项目,它以非常相似的方式执行多个操作(订阅完成事件、执行任务、取消订阅完成事件以及处理取消、超时等),因此我决定编写一个实用程序类来处理该执行。然而,我遇到了一个我不理解的场景,因此不知道如何修复

这段过于简化的代码说明了问题:

class Program
{
    static void Main(string[] args)
    {
        Do();
        Console.Read();
    }

    private static async Task Do()
    {
        var task = new Task(async() => await Operation()/*this throws and terminates the application*/);

        try
        {
            await OperationExecuter.ExecuteAsync(task);
        }
        catch (InvalidOperationException)
        {
            //I expected the exception to be caught here
        }
    }


    static async Task Operation()
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    }
}

class OperationExecuter
{
    public static async Task ExecuteAsync(Task task)
    {
        task.Start();
        await task; //I expected the exception to be unwrapped and thrown here
    }
}
我还尝试了任务,比如
var task=newtask(()=>Operation())
但该异常永远不会被处理(尽管它不会终止应用程序,因为它不会在主线程中引发)

我如何正确处理异常

将实现更改为执行操作会产生相同的结果:

class Program
{
    static void Main(string[] args)
    {
        Do();
        Console.Read();
    }

    private static async Task Do()
    {
        var action = new Action(async () => await Operation() /*this throws and terminates the application*/);

        try
        {
            await OperationExecuter.ExecuteAsync(action);
        }
        catch (InvalidOperationException)
        {
            //I expected the exception to be caught here
        }
    }


    static async Task Operation()
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    }
}

class OperationExecuter
{
    public static async Task ExecuteAsync(Action action)
    {
        await Task.Run(action); //I expected the exception to be unwrapped and thrown here
    }
}
对于好奇的人来说,一个更现实的
操作执行器应该是:

class Program
{
    static void Main(string[] args)
    {
        Do();
        Do2();
        Console.Read();
    }

    private static async Task Do()
    {
        var service = new Service(new Hardware());
        try
        {
            await
                OperationExecuter.ExecuteAsync(service, handler => service.Operation1Completed += handler,
                    handler => service.Operation1Completed += handler, async () => await service.Operation1(),
                    CancellationToken.None);
        }
        catch (InvalidOperationException)
        {
            //Exception is caught!!!
        }
    }

    private static async Task Do2()
    {
        var service = new Service(new Hardware());
        try
        {
            await
                OperationExecuter.ExecuteAsync(service, handler => service.Operation1Completed += handler,
                    handler => service.Operation1Completed += handler, async () => await service.Operation2(60),
                    CancellationToken.None);
        }
        catch (InvalidOperationException)
        {
            //Exception is caught!!!
        }
    }
}

internal class OperationExecuter
{
    public static async Task ExecuteAsync(Service service, Action<EventHandler> subscriptionAction,
        Action<EventHandler> unsubscriptionAction, Func<Task> sendCommandAction, CancellationToken cancellationToken)
    {
        var commandCompletionSource = new TaskCompletionSource<bool>();
        var hardwareFailureCompletionSource = new TaskCompletionSource<bool>();

        cancellationToken.Register(() => commandCompletionSource.SetCanceled());

        var eventHandler = new EventHandler((sender, args) =>
        {
            commandCompletionSource.SetResult(true);
        });

        service.HardwareFailure += (sender, args) => hardwareFailureCompletionSource.SetResult(false);

        subscriptionAction(eventHandler);

        try
        {
            await Task.Run(sendCommandAction, cancellationToken);
            await Task.WhenAny(commandCompletionSource.Task, hardwareFailureCompletionSource.Task);

            //same for disconnection, etc
            if (hardwareFailureCompletionSource.Task.IsCompleted)
            {
                throw new HardwareFailureException();
            }
        }
        finally
        {
            unsubscriptionAction(eventHandler);
        }
    }
}

class HardwareFailureException : Exception
{
}

class Service
{
    private readonly Hardware hardware;

    public Service(Hardware hardware)
    {
        this.hardware = hardware;
    }

    public async Task Operation1() //something like sending command to hardware
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    }

    public event EventHandler Operation1Completed;

    public async Task Operation2(int someParameter)
    {
        await Task.Delay(1000);
        throw new InvalidOperationException();
    }

    public event EventHandler Operation2Completed;

    public event EventHandler LostConnection;

    public event EventHandler HardwareFailure;
}

class Hardware
{
}
类程序
{
静态void Main(字符串[]参数)
{
Do();
Do2();
Console.Read();
}
私有静态异步任务Do()
{
var service=新服务(新硬件());
尝试
{
等待
OperationExecuter.ExecuteAsync(服务,处理程序=>service.Operation1Completed+=handler,
handler=>service.Operation1Completed+=handler,async()=>wait service.Operation1(),
取消令牌(无);
}
捕获(无效操作异常)
{
//异常被捕获!!!
}
}
专用静态异步任务Do2()
{
var service=新服务(新硬件());
尝试
{
等待
OperationExecuter.ExecuteAsync(服务,处理程序=>service.Operation1Completed+=handler,
handler=>service.Operation1Completed+=handler,async()=>wait service.Operation2(60),
取消令牌(无);
}
捕获(无效操作异常)
{
//异常被捕获!!!
}
}
}
内部类操作执行器
{
公共静态异步任务ExecuteAsync(服务服务、操作订阅操作、,
操作取消订阅操作,Func sendCommandAction,CancellationToken CancellationToken)
{
var commandCompletionSource=new TaskCompletionSource();
var hardwareFailureCompletionSource=new TaskCompletionSource();
cancellationToken.Register(()=>commandCompletionSource.SetCancelled());
var eventHandler=新的eventHandler((发送方,参数)=>
{
commandCompletionSource.SetResult(true);
});
service.HardwareFailure+=(发送方,参数)=>hardwareFailureCompletionSource.SetResult(false);
subscriptionAction(eventHandler);
尝试
{
等待任务。运行(sendCommandAction、cancellationToken);
wait Task.whenay(commandCompletionSource.Task,hardwareFailureCompletionSource.Task);
//同样适用于断开连接等
if(硬件版本ailureCompletionSource.Task.IsCompleted)
{
抛出新的HardwareFailureException();
}
}
最后
{
取消订阅操作(eventHandler);
}
}
}
类HardwareFailureException:异常
{
}
班级服务
{
专用只读硬件;
公共服务(硬件)
{
this.hardware=硬件;
}
公共异步任务操作1()//类似于向硬件发送命令
{
等待任务。延迟(1000);
抛出新的InvalidOperationException();
}
公共事件处理程序操作1已完成;
公共异步任务操作2(int-someParameter)
{
等待任务。延迟(1000);
抛出新的InvalidOperationException();
}
公共事件事件处理程序操作2已完成;
公共事件处理程序丢失连接;
公共事件处理程序硬件文档;
}
类硬件
{
}

问题是因为您实际创建了一个
任务
,而您只
等待
外部
任务
。这就是为什么不应该使用
任务
构造函数的原因之一。相反,请使用
Task.Run
,它会意识到这一点并为您打开outter任务:

private static async Task Do()
{
    var task = Task.Run(async() => await Operation());
    try
    {
        await OperationExecuter.ExecuteAsync(task);
    }
    catch (InvalidOperationException)
    {
        //I expected the exception to be caught here
    }
}
编辑

@Servy正确地指出,除非有特别好的理由,否则您要将
任务
包装为
任务。运行
,您可以将所有内容保存在一起,只需等待创建的
任务
,就可以省去所有展开的麻烦:

public class OperationExecuter
{
    public static async Task ExecuteAsync(Func<Task> func)
    {
        await func();
    }
}
公共类操作执行器
{
公共静态异步任务ExecuteAsync(Func Func)
{
等待函数();
}
}

重要的是,
OperationExecuter
可以控制何时启动任务。我想我真的应该让
操作执行器
采取行动并使用
任务。然后运行(行动)
,对吗?然后您应该更改实现。你不应该把冷淡的任务传给别人。例如,两次使用
Task.Start
将产生异常,这也违反了指导原则。相反您可以将代理排队到您的
OperationExecutor
并让它使用
任务。改为运行
。不幸的是,更改实现使
OperationExecuter
采用
操作
并使用
任务。运行
会产生相同的结果。@Joao这是因为
操作
转换为
异步作废
。您需要接受任务对象中的
Func
@ErikE
wait
unwraps聚合异常。试试看,它很漂亮