如何使用明确构造的任务处理异常

约翰·德格里迪森

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

此过度简化的代码说明了该问题:

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
{
}
尤瓦尔·伊茨恰科夫(Yuval Itzchakov)

问题是由于您实际上创建了一个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();
    }
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章