I donβt think there is one wonderful answer here: it depends somewhat on what you want to do.
First of all, we must decide whether consumers expect SomeMethodAsync() to contract quickly, because it is an asynchronous method. In this case, if SomeMethod() slow, we can use Task.Run() to achieve this, although this is often considered bad practice:
// if you think that "returns quickly" is an important guarantee of SomeMethodAsync public Task<int> SomeMethodAsync(CancellationToken cancellationToken) { return Task.Run(() => SomeMethod(), cancellationToken); }
You can see that MSFT takes this approach with the standard default implementation of TextReader.ReadAsync .
If SomeMethod() is fast enough or we donβt care about returning fast (technically asynchronous methods do not guarantee), then we need some approach that mimics the behavior of the async method, which is executed synchronously. In particular, crashes / cancellations should lead to crashes / cancellations of tasks. One way to do this is to simply use the async method that you are showing:
public async Task<int> SomeMethodAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return SomeMethod(); }
The advantage of this approach is that it is very simple and at the same time completely correct. Another advantage is that some primitive task results are cached by the runtime in such a way that they are not used when using Task.FromResult . For example, a null task is cached:
public async Task<int> Z() => 0; void Main() { Console.WriteLine(Task.FromResult(0) == Task.FromResult(0));
This way, you can get some performance benefits if you often return these common values.
The main disadvantage of this is that it will generate an assembly warning, since you have an async method with no expectations. You can suppress it with #pragma , but it makes the code ugly and can still confuse other developers reading the code. Another small drawback is that it is likely to be marginally less productive than in some cases manually creating tasks. For example, in the case when the token with the transfer is canceled, we must report this by throwing an exception.
This leads us to something similar to your second option, slightly tweaked to handle the cancellation correctly:
public Task<int> SomeMethodAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<int>(cancellationToken); } try { return Task.FromResult(SomeMethod()); } catch (OperationCanceledException oce) { var canceledTaskBuilder = new TaskCompletionSource<int>(); canceledTaskBuilder.SetCanceled(); return canceledTaskBuilder.Task; } catch (Exception e) { return Task.FromException<int>(e); } }
This is pretty awkward, so most of the time I would go with one of the first two options or write a helper method to wrap the code from the third option.