How to reuse synchronization code in an asynchronous method using C #

Assume an interface and consider this following code, this is my contract

public interface ISomeContract { Task<int> SomeMethodAsync(CancellationToken cancellationToken); int SomeMethod(); } 

now imagine a contract implementation of ISomeContract with the following code

 public class SomeImplementation : ISomeContract { public int SomeMethod() { // lots of codes ... return 10; } public async Task<int> SomeMethodAsync(CancellationToken cancellationToken) { return SomeMethod(); // another example: I can remove async modifier from SomeMethodAsync method and this following. //try //{ // return Task.FromResult<int>(SomeMethod()); //} //catch (Exception ex) //{ // return Task.FromException<int>(ex); //} } } 

As you can see, I do not have pending code. How can I reuse the body of SomeMethod in case I do not have pending code? I tested even more Task.FromResult without an async character in the method header, but how can I achieve the best solution to this problem?

+5
source share
3 answers

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)); // false Console.WriteLine(Z() == Z()); // true } 

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.

+2
source

I want to add that IF you do IO in your SomeMethod , you do not want to reuse it (using Task.Run or any other method). Instead, you want to rewrite it using asynchronous I / O, because one of the main points of using async in general is to use this asynchronous I / O. For example, suppose you have this:

 public long SomeMethod(string url) { var request = (HttpWebRequest)WebRequest.Create(url); var response = request.GetResponse(); return response.ContentLength; } 

In this case, you do not want to reuse this method in your SomeMethodAsync , because you are executing request.GetResponse() , which is an IO, and has an asynchronous version. Therefore, you should do it as follows:

 public async Task<long> SomeMethodAsync(string url, CancellationToken cancellationToken) { var request = (HttpWebRequest) WebRequest.Create(url); using (cancellationToken.Register(() => request.Abort(), false)) { try { var response = await request.GetResponseAsync(); return response.ContentLength; } catch (WebException ex) { if (cancellationToken.IsCancellationRequested) throw new OperationCanceledException(ex.Message, ex, cancellationToken); throw; } } } 

As you can see in this case, a little longer (because GetResponseAsync does not accept cancellation tokens), but that would be correct if you use any IO.

+1
source

Here is how I do it:

 async Task Main() { Do(3); await DoAsync(3); } int Do(int value) { return DoHelper<int>(value, t => t); } Task<int> DoAsync(int value) { return DoHelper<Task<int>>(value, Task.FromResult); } T DoHelper<T>(int value, Func<int, T> transform) { // common code goes in this function return transform(value); } 

DoHelper contains common code. transform is magic. Non-asynchronous functions return the same value that was passed. Asynchronous functions transfer the result to Task.FromResult '.

0
source

Source: https://habr.com/ru/post/1260009/


All Articles