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

标签 c# exception async-await task-parallel-library

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

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

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 = new Task(() => 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
    }
}

对于好奇的人来说,更现实的 OperationExecuter 应该是这样的:

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
{
}

最佳答案

问题是由于您实际上创建了一个 Task<Task>。 ,而你只有await外部Task .这是您不应该使用 Task 的原因之一。构造函数。相反,使用 Task.Run ,它知道这一点并将为您解包外部任务:

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 正确地指出,除非有特别好的理由,否则您要包装 TaskTask.Run ,您可以将它们全部保存在一起并简单地 await在创建Task一起省去解包的麻烦:

public class OperationExecuter
{
    public static async Task ExecuteAsync(Func<Task> func)
    {
        await func();
    }
}

关于c# - 如何使用显式构造的任务处理异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32506652/

相关文章:

c# - 消息对话框使用 MahApps.Metro 和 MVVM Light Toolkit 通过 DialogCoordinator 触发 ViewModel

c# - System.Net.WebRequest - 超时错误

c# - 在 WPF 应用程序中全局捕获异常?

javascript - mqtt 异步等待消息然后响应 http post 请求

c# - 本地化 Win7 版本上的 UI 截断

c# - 我可以用用户输入创建一个 t4 文件吗?

exception - 升级到.net core 2.2之后,通过EF插入失败并出现错误

java - boolean 标志上的 Spring 重试策略

javascript - 将 promise 转换为异步/等待 - Javascript

dart - 这个带有异步的 getter 变量如何在 DART 中工作