The methods that return Task
have two options for reporting an error:
I was having very similar issue/doubts. I was trying to implement asynchronous methods (e.g. public Task DoSomethingAsync()
) which was specified in an interface. To rephrase, the interface expects the particular function (DoSomething
) to be asynchronous.
However, it turns out that the implementation could do it synchronously (and I don't think it method is going to take very long to complete either).
public interface IFoobar
{
Task DoSomethingAsync(Foo foo, Bar bar);
}
public class Caller
{
public async void Test
{
try
{
await new Implementation().DoSomethingAsync(null, null);
}
catch (Exception e)
{
Logger.Error(e);
}
}
}
Now there are four ways that I could do this.
Method 1:
public class Implementation : IFoobar
{
public Task DoSomethingAsync(Foo foo, Bar bar)
{
if (foo == null)
throw new ArgumentNullException(nameof(foo));
if (bar == null)
throw new ArgumentNullException(nameof(bar));
DoSomethingWithFoobar(foo, bar);
}
}
Method 2:
public class Implementation : IFoobar
{
#pragma warning disable 1998
public async Task DoSomethingAsync(Foo foo, Bar bar)
{
if (foo == null)
throw new ArgumentNullException(nameof(foo));
if (bar == null)
throw new ArgumentNullException(nameof(bar));
DoSomethingWithFoobar(foo, bar);
}
#pragma warning restore 1998
}
Method 3:
public class Implementation : IFoobar
{
public Task DoSomethingAsync(Foo foo, Bar bar)
{
if (foo == null)
return Task.FromException(new ArgumentNullException(nameof(foo)));
if (bar == null)
return Task.FromException(new ArgumentNullException(nameof(bar)));
DoSomethingWithFoobar(foo, bar);
return Task.CompletedTask;
}
}
Method 4:
public class Implementation : IFoobar
{
public Task DoSomethingAsync(Foo foo, Bar bar)
{
try
{
if (foo == null)
throw new ArgumentNullException(nameof(foo));
if (bar == null)
throw new ArgumentNullException(nameof(bar));
}
catch (Exception e)
{
return Task.FromException(e);
}
DoSomethingWithFoobar(foo, bar);
return Task.CompletedTask;
}
}
Like what Stephen Cleary has mentioned, all of these generally works. However, there are some differences.
task.ContinueWith(task => {})
) to handle exception, the continuation would simply not run. This is similar to the example in your question.#pragma
suppressions. The method could end up running asynchronously, causing unnecessary context switches.DoSomethingAsync()
at all! All you could see is the caller. This could be rather bad depending on how many exceptions of the same type you are throwing.await
+catch
the exception; you could do task continuations; the stacktrace doesn't have missing information. It's also running synchronously, which can be useful for very lightweight/quick methods. But... it's awfully awkward to write it this way for every method you implement.Note that I'm discussing this from the implementation's point of view - I have no control how someone else could call my method. The aim is to implement in a way that executes normally, independent on the method of calling.