不带样板的异步代码中的简单常规异常处理

时间:2019-09-17 12:20:18

标签: c# async-await cancellation-token

我们开始在应用外大量使用CancellationToken,因此我们必须相应地更改异常处理:

class Program
{
    static async Task Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource(100);
        await DoJob(cts.Token);
        Console.WriteLine("Successfully finished");
    }

    private static async Task DoJob(CancellationToken ct)
    {
        try
        {
            await Task.Delay(1000, ct);
        }
        catch (Exception e) when(!(e is OperationCanceledException))
        {
            Console.WriteLine("Do cleanup in case of error.");
        }
    }
}

此代码背后的想法是,如果有人使用catch(Exception e)(请不要为此怪我)而忘了排除CancellationToken,则会执行错误处理,例如,记录操作失败。但这不是真的,id不会失败,只是被取消了。取消处理应与失败处理不同。

在我看来,在每个常规渔获物上写东西实际上都是一个大样板

catch (Exception e) when(!(e is OperationCanceledException))

是否有一些更少样板的更强大的解决方案?

3 个答案:

答案 0 :(得分:3)

您可以创建一个接受Func<Task>并捕获异常的方法,例如:

class Program
{
    static async Task Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource(100);
        await GeneralDoJobAndCatchException(() => DoJob(cts.Token));
        Console.WriteLine("Successfully finished");
    }

    private static async Task GeneralDoJobAndCatchException(Func<Task> func)
    {
        try
        {
            await func();
        }
        catch (OperationCanceledException) { }
        catch (Exception e)
        {
            Console.WriteLine("Do error handling");
        }
    }

    private static async Task DoJob(CancellationToken ct)
    {
        await Task.Delay(1000, ct);
    }
}

答案 1 :(得分:1)

我们遇到了完全相同的问题。主要是有一个while循环检查CancellationToken,但您必须捕获此异常。

我们创建了以下扩展方法:

public static async Task<TaskStatus> HideCancellationException(this Task task)
{
    try
    {
        await task;
        return task.Status;
    }
    catch (OperationCanceledException)
    {
        return TaskStatus.Canceled;
    }
}

具有此扩展方法可以更改此代码:

while (!cancellationToken.IsCancellationRequested)
{
    // do stuff here...

    try
    {   
        await Task.Delay(..., cancellationToken);
    }
    catch (OperationCanceledException)
    {
        // expected
    }
}

类似这样:

while (!cancellationToken.IsCancellationRequested)
{
    // Do stuff here.

    await Task.Delay(..., cancellationToken).HideCancellationException();
}

请记住,Task<T>显然没有过载,因为取消的情况下返回值为default。您无法区分default作为正常任务结果和default作为取消结果。在这种情况下,最好捕获异常。

答案 2 :(得分:0)

通过Task.WhenAny间接等待,然后查询已完成任务的状态,可以完全摆脱try-catch块:

private static async Task DoJob(CancellationToken ct)
{
    var completedTask = await Task.WhenAny(Task.Delay(1000, ct));
    if (completedTask.IsFaulted)
    {
        Console.WriteLine("Error: " + completedTask.Exception.InnerException);
    }
    else if (completedTask.IsCanceled)
    {
        // Do nothing
    }
    else // Success
    {
        // Do nothing
    }
}