dotnet/runtime

[Analyzer/fixer suggestion]: Exceptions thrown inside async methods should be wrapped by Task.FromException

Open

#70,905 创建于 2022年6月17日

在 GitHub 查看
 (7 评论) (2 反应) (0 负责人)C# (5,445 fork)batch import
api-approvedarea-System.Threading.Taskscode-analyzercode-fixerhelp wanted

仓库指标

Star
 (17,886 star)
PR 合并指标
 (平均合并 12天 11小时) (30 天内合并 661 个 PR)

描述

Sparked from this question: https://github.com/dotnet/runtime/pull/70574#discussion_r900368652

Exceptions thrown inside methods that return Task or ValueTask should be wrapped by Task.FromException, except if they are ArgumentNullException or ArgumentException.

If the method is marked as async, we could also await the Task.FromException.

Suggested category: Reliability Suggested level: Warning (Same as CA2007) Suggested milestone: 8.0

Examples

Method that returns Task

Before

// Task
private Task MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg);
    if (/* some condition */)
    {
        throw new InvalidOperationException("Some message");
    }
    return SomethingAsync(cancellationToken);
}

// async Task
private async Task MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg);
    if (/* some condition */)
    {
        throw new InvalidOperationException("Some message");
    }
    await SomethingAsync(cancellationToken).ConfigureAwait(false);
}

After

// Task
private Task MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg); // Do not change this
    if (/* some condition */)
    {
        return Task.FromException(new InvalidOperationException("Some message")); // wrap this
    }
    return SomethingAsync(cancellationToken);
}

// async Task
private Task MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg); // Do not change this
    if (/* some condition */)
    {
        // wrap this, await it, but don't add ConfigureAwait(false), that should be added by CA2007 separately
        await Task.FromException(new InvalidOperationException("Some message"));
    }
    await SomethingAsync(cancellationToken).ConfigureAwait(false);
}

Method that returns ValueTask

Before

// ValueTask
private ValueTask MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg);
    if (/* some condition */)
    {
        throw new InvalidOperationException("Some message");
    }
    return SomethingAsync(cancellationToken);
}

// async ValueTask
private async ValueTask MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg);
    if (/* some condition */)
    {
        throw new InvalidOperationException("Some message");
    }
    await SomethingAsync(cancellationToken).ConfigureAwait(false);
}

After

private ValueTask MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg); // Do not change this
    if (/* some condition */)
    {
        return ValueTask.FromException(new InvalidOperationException("Some message")); // wrap this
    }
    return SomethingAsync(cancellationToken);
}

// async ValueTask
private async ValueTask MyMethodAsync(string arg, CancellationToken cancellationToken)
{
    ArgumentException.ThrowIfNullOrEmpty(arg);
    if (/* some condition */)
    {
        // wrap this, await it, but don't add ConfigureAwait(false), that should be added by CA2007 separately
        await ValueTask.FromException(new InvalidOperationException("Some message"));
    }
    await SomethingAsync(cancellationToken).ConfigureAwait(false);
}

cc @buyaa-n

贡献者指南