I'm writing a class which have synchronous and asynchronous versions of the same method
void MyMethod(object argument)
and Task MyMethodAsync(object argument)
. In sync version I validate argument with the simple check
if (argument == null)
throw new ArgumentNullException("argument");
How should the same check look like in async method?
1) Same as for sync method
2) (Updated after first answer)
if (argument == null)
return new Task.Factory.StartNew(() => { throw new ArgumentNullException("argument"); });
That depends a bit on when you want the error to be raised - i.e. eagerly, or as part of the awaitable. As with iterator blocks, if you want eager error checks, you need two methods, for example:
public Task<int> SomeMethod(..args..) {
if(..args fail..) throw new InvalidOperationException(...);
return SomeMethodImpl(...args...);
}
private async Task<int> SomeMethodImpl(...args...)
{
... await etc ...
}
This will then perform any argument checking as part of the initial call, not the awaitable. If you want the exception to be part of the awaitable, you can just throw it:
public async Task<int> SomeMethod(..args..) {
if(..args fail..) throw new InvalidOperationException(...);
... await etc ...
}
However, in your example, the fact that you are return
ing a Task
suggests that this is not actually an async
method, but is an async (but not async
) method. You can't just do:
return new Task(() => { throw new ArgumentNullException("argument"); });
because that Task
will never have been started - and never will be. I suspect you would need to do something like:
try {
throw new InvalidArgumentException(...); // need to throw to get stacktrace
} catch(Exception ex) {
var source = new TaskCompletionSource<int>();
source.SetException(ex);
return source.Task;
}
which is... a bit of a mouthful and could probably be encapsulated a bit better. This will return a Task
that indicates it is in the Faulted
state.
Starting from C# 7.0, you can use use local function to reduce noise in code but still be compliant with argument check practice from sonar rule S4457. For example, this code will throw an ArgumentNullException in both cases: if you call it with await or without.
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();
}
As per sonar rule S4457
Because of the way async/await methods are rewritten by the compiler, any exceptions thrown during the parameters check will happen only when the task is observed. That could happen far away from the source of the buggy code or never happen for fire-and-forget tasks.
Therefore it is recommended to split the method into two: an outer method handling the parameter checks (without being async/await) and an inner method to handle the iterator block with the async/await pattern.
This rule raises an issue when an async method throws any exception derived from ArgumentException and contains await keyword.
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; }
}
}
Simply throw it like you did in the sync method, the TPL has various mechanisms in place to re-throw exception, for example when you read the .Result
property or the access .Exception
property.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With