15

我正在编写一个具有相同方法的同步和异步版本的类, void MyMethod(object argument)并且Task MyMethodAsync(object argument). 在同步版本中,我通过简单检查验证参数

if (argument == null)
    throw new ArgumentNullException("argument");

在异步方法中相同的检查应该如何?

1) 与同步方法相同

2)(第一次回答后更新)

if (argument == null)
    return new Task.Factory.StartNew(() => { throw new ArgumentNullException("argument"); });
4

4 回答 4

13

这在一定程度上取决于您希望何时引发错误 - 即急切地提出错误,或者作为等待的一部分。与迭代器块一样,如果您想要急切的错误检查,则需要两种方法,例如:

public Task<int> SomeMethod(..args..) {
    if(..args fail..) throw new InvalidOperationException(...);
    return SomeMethodImpl(...args...);
}
private async Task<int> SomeMethodImpl(...args...)
{
    ... await etc ...
}

然后,这将执行任何参数检查作为初始调用的一部分,而不是等待。如果您希望异常成为可等待的一部分,则可以将其抛出:

public async Task<int> SomeMethod(..args..) {
    if(..args fail..) throw new InvalidOperationException(...);
    ... await etc ...
}

但是,在您的示例中,您正在使用 a 的事实return表明Task这实际上不是一种async方法,而是一种异步(但不是async)方法。你不能这样做:

return new Task(() => { throw new ArgumentNullException("argument"); });

因为那Task永远不会开始——而且永远不会。我怀疑您需要执行以下操作:

try {
    throw new InvalidArgumentException(...); // need to throw to get stacktrace
} catch(Exception ex) {
    var source = new TaskCompletionSource<int>();
    source.SetException(ex);
    return source.Task;
}

这是……有点拗口,可能封装得更好一点。这将返回一个Task表明它处于Faulted状态的。

于 2013-09-06T11:22:34.887 回答
13

从 C# 7.0 开始,您可以使用本地函数来减少代码中的噪音,但仍符合声纳规则 S4457中的参数检查实践。例如,此代码在两种情况下都会抛出 ArgumentNullException:无论是否使用 await 调用它。

private Task WaitSeconds(int? durationInSeconds)
{
    if(durationInSeconds == null) throw new ArgumentNullException(nameof(durationInSeconds));
    async Task WaitSecondsInternal()
    {
        await Task.Delay(TimeSpan.FromSeconds(durationInSeconds.Value));
    }
    return WaitSecondsInternal();
}
于 2019-12-05T09:02:01.413 回答
5

根据声纳规则 S4457

由于编译器重写 async/await 方法的方式,在参数检查期间抛出的任何异常都只会在观察到任务时发生。这可能发生在远离错误代码源的地方,或者永远不会发生在即发即弃的任务中。

因此,建议将该方法拆分为两个:一个处理参数检查的外部方法(不是 async/await)和一个处理具有 async/await 模式的迭代器块的内部方法。

当异步方法抛出派生自 ArgumentException 并包含 await 关键字的任何异常时,此规则会引发问题。

不合规代码示例

public static async Task SkipLinesAsync(this TextReader reader, int linesToSkip) // Noncompliant
{
    if (reader == null) { throw new ArgumentNullException(nameof(reader)); }
    if (linesToSkip < 0) { throw new ArgumentOutOfRangeException(nameof(linesToSkip)); }

    for (var i = 0; i < linesToSkip; ++i)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) { break; }
    }
}

合规解决方案

public static Task SkipLinesAsync(this TextReader reader, int linesToSkip)
{
    if (reader == null) { throw new ArgumentNullException(nameof(reader)); }
    if (linesToSkip < 0) { throw new ArgumentOutOfRangeException(nameof(linesToSkip)); }

    return reader.SkipLinesInternalAsync(linesToSkip);
}

private static async Task SkipLinesInternalAsync(this TextReader reader, int linesToSkip)
{
    for (var i = 0; i < linesToSkip; ++i)
    {
        var line = await reader.ReadLineAsync().ConfigureAwait(false);
        if (line == null) { break; }
    }
}
于 2019-01-29T10:41:39.453 回答
3

就像你在同步方法中所做的那样简单地抛出它,TPL 有各种机制来重新抛出异常,例如当你阅读 . Result财产或访问权。Exception财产。

于 2013-09-06T11:23:29.803 回答