A sample method with XML documentation:
// summary and param tags are here when you're not looking.
/// <exception cref="ArgumentNullException>
/// <paramref name="text" /> is null.
/// </exception>
public void Write(string text)
{
if (text == null)
throw new ArgumentNullException("text", "Text must not be null.");
// sync stuff...
}
Write(null)
throws an exception as expected. Here is an asynchronous method:
public async Task WriteAsync(string text)
{
if (text == null)
throw new ArgumentNullException("text", "Text must not be null.");
// async stuff...
}
WriteAsync(null)
, won't throw an exception until awaited. Should I specify the ArgumentNullException
in an exception
tag anyway? I think it would make the consumer think that calling WriteAsync
may throw an ArgumentNullException
and write something like this:
Task t;
try
{
t = foo.WriteAsync(text);
}
catch (ArgumentNullException)
{
// handling stuff.
}
What is the best practice for documenting exceptions in asynchronous methods?
To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block. Uncomment the throw new Exception line in the example to demonstrate exception handling. The task's IsFaulted property is set to True , the task's Exception.
As we know, in asynchronous programming, control does not wait for the function's result and it executes the next line. So when the function throws an exception, at that moment the program control is out of the try-catch block.
When you await a Task, the first exception is re-thrown, so you can catch the specific exception type (such as InvalidOperationException). However, when you synchronously block on a Task using Task. Wait or Task. Result, all of the exceptions are wrapped in an AggregateException and thrown.
Not a direct answer, but personally I'd advise leaning towards fast-fail here; this might mean writing 2 methods:
public Task WriteAsync(string text) // no "async"
{
// validation
if (text == null)
throw new ArgumentNullException("text", "Text must not be null.");
return WriteAsyncImpl(text);
}
private async Task WriteAsyncImpl(string text)
{
// async stuff...
}
This pattern is also an ideal place to add "fast path" code, for example:
public Task WriteAsync(string text) // no "async"
{
// validation
if (text == null)
throw new ArgumentNullException("text", "Text must not be null.");
if (some condition)
return Task.FromResult(0); // or similar; also returning a pre-existing
// Task instance can be useful
return WriteAsyncImpl(text);
}
Microsoft doesn't seem to differentiate between the async
method throwing an exception and the returned Task
having an exception stored in its Exception
property. E.g.:
WebClient.DownloadFileTaskAsync(string, string)
Personally, I would choose to document the exceptions as part of the documentation for the return value (i.e. the returned Task
), since the distinction may be important for clients.
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