【问题标题】:Why is wrapping parameter validation in a non-async method desirable?为什么在非异步方法中包装参数验证是可取的?
【发布时间】:2021-11-15 23:33:49
【问题描述】:

我在 SonarQube 中弹出了 this 警告,但我不明白为什么需要包装验证。

我已阅读以下问题,但似乎都没有清楚地解释为什么它更好?

Parameter validation in "async"/"await" methods should be wrapped

Validate parameters in async method

在示例中

public async Task DoSomethingAsync(string param){
    if(string.IsNullOrEmpty(param)
    {
        throw new ArgumentException("Param is blank");
    }

    await DoSomethingElseAsync(param);
}

为什么这会以不同的方式执行

public Task DoSomethingAsync(string param){
    if(string.IsNullOrEmpty(param)
    {
        throw new ArgumentException("Param is blank");
    }

    return doSomethingElseAsync(param);
}

鉴于调用者都需要等待这两个实现,是否真的可以保证第二个实现中的验证会立即执行,而不是像任何其他异步方法一样被延迟?

【问题讨论】:

  • 它归结为异常是如何抛出的:首先,它们不会被抛出给调用者,而是作为返回的 Task 的 Exception 属性返回。第二种是直接扔。考虑var t = DoSomethingAsync(...); await t;:对于第一个sn-p,await 抛出异常。其次,对DoSomethingAsync 的调用本身会抛出
  • 哪个更好...是个人喜好。例如,Bill Wagner 更喜欢您的第二种方法。如果没记错的话(我目前找不到参考资料),Stephen Toub 更喜欢第一个。
  • 您可以换一种方式来考虑这个问题:让您的公共 API 尽可能精简。您的公共 API 会执行初步检查,然后从 API 使用者的角度来看,其他一切都是实施细节。
  • "实际上是否可以保证第二个实现中的验证会立即执行,而不是像任何其他异步方法一样被延迟?" - 是的,因为该方法实际上不使用async,因此甚至没有从它构建的异步状态机。

标签: c# async-await sonarqube


【解决方案1】:

假设调用者会依次调用和await异步方法,没有区别。

await DoSomethingAsync("Chess");
await DoSomethingAsync("Stratego");
await DoSomethingAsync(null);

但是你怎么能确定呢?调用者不妨将创建任务的阶段和等待它们的阶段分开。

var tasks = new List<Task>();
tasks.Add(DoSomethingAsync("Chess"));
tasks.Add(DoSomethingAsync("Stratego"));
tasks.Add(DoSomethingAsync(null));
await Task.WhenAll(tasks);

在这种情况下,两个不同的DoSomethingAsync 实现将导致调用者代码中的不同行为。

另外一个区别是方法的XML documentation,假设你想和Microsoft's guidelines保持一致。第一个实现不需要&lt;exception&gt;元素,因为不能同步抛出异常:

/// <summary>Does something asynchronously.</summary>
/// <param name="param">The parameter.</param>
/// <returns>A <see cref="Task"/> that will complete when something is done.</returns>
public async Task DoSomethingAsync(string param)
{
    // ...
}

第二个实现确实需要&lt;exception&gt; 元素,因为ArgumentException 可以同步抛出:

/// <summary>Does something asynchronously.</summary>
/// <param name="param">The parameter.</param>
/// <returns>A <see cref="Task"/> that will complete when something is done.</returns>
/// <exception cref="ArgumentException"><paramref name="param"/> is blank.</exception>
public Task DoSomethingAsync(string param)
{
    // ...
}

【讨论】:

    【解决方案2】:

    编译器将异步方法转换为状态机。

    当方法被调用时,状态机被实例化。只有这样,第一个await 之前的代码才会被调用。

    如果你有这个方法:

    private static async Task<int> GetValueAsync(object o)
    {
        if (o is null) throw new ArgumentNullException(nameof(o));
        await Task.Delay(1);
        return 0;
    }
    

    生成的代码将是:

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <GetValueAsync>d__0 : IAsyncStateMachine
    {
        public int <>1__state;
    
        public AsyncTaskMethodBuilder<int> <>t__builder;
    
        public object o;
    
        private TaskAwaiter <>u__1;
    
        private void MoveNext()
        {
            int num = <>1__state;
            int result;
            try
            {
                TaskAwaiter awaiter;
                if (num != 0)
                {
                    if (o == null)
                    {
                        throw new ArgumentNullException("o");
                    }
                    awaiter = Task.Delay(1).GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
                result = 0;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult(result);
        }
    
        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }
    
        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
            <>t__builder.SetStateMachine(stateMachine);
        }
    
        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }
    
    [AsyncStateMachine(typeof(<GetValueAsync>d__0))]
    private static Task<int> GetValueAsync(object o)
    {
        <GetValueAsync>d__0 stateMachine = default(<GetValueAsync>d__0);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.o = o;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }
    

    如果您在非异步方法中验证参数:

    private static Task<int> GetValueAsync(object o)
    {
        if (o is null) throw new ArgumentNullException(nameof(o));
        return GetValueAsyncImpl(o);
    
        static async Task<int> GetValueAsyncImpl(object o)
        {
            await Task.Delay(1);
            return 0;
        }
    }
    

    生成的代码将是:

    [StructLayout(LayoutKind.Auto)]
    [CompilerGenerated]
    private struct <<GetValueAsync>g__GetValueAsyncImpl|0_0>d : IAsyncStateMachine
    {
        public int <>1__state;
    
        public AsyncTaskMethodBuilder<int> <>t__builder;
    
        private TaskAwaiter <>u__1;
    
        private void MoveNext()
        {
            int num = <>1__state;
            int result;
            try
            {
                TaskAwaiter awaiter;
                if (num != 0)
                {
                    awaiter = Task.Delay(1).GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter);
                    num = (<>1__state = -1);
                }
                awaiter.GetResult();
                result = 0;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <>t__builder.SetResult(result);
        }
    
        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }
    
        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
            <>t__builder.SetStateMachine(stateMachine);
        }
    
        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }
    
    private static Task<int> GetValueAsync(object o)
    {
        if (o == null)
        {
            throw new ArgumentNullException("o");
        }
        return <GetValueAsync>g__GetValueAsyncImpl|0_0(o);
    }
    
    [AsyncStateMachine(typeof(<<GetValueAsync>g__GetValueAsyncImpl|0_0>d))]
    [CompilerGenerated]
    internal static Task<int> <GetValueAsync>g__GetValueAsyncImpl|0_0(object o)
    {
        <<GetValueAsync>g__GetValueAsyncImpl|0_0>d stateMachine = default(<<GetValueAsync>g__GetValueAsyncImpl|0_0>d);
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }
    

    如果有无效的参数值,它不会实例化状态机。

    迭代器方法也是如此。

    【讨论】:

    • 这并没有触及实际的关键区别,即异常是如何向上传播的:通过被抛出,与被设置为任务的异常属性
    • 谢谢。这确实很好地解释了差异。我想这也意味着第二个示例也会更快,因为它制作了一个状态机而不是 2 个?尽管除非在一个紧密的循环中,否则可能无法测量。
    猜你喜欢
    • 2013-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多