Template for writing synchronous and asynchronous methods in libraries and DRY storage

I am changing the library to add async methods. From Should I expose synchronous wrappers for asynchronous methods? he claims that I should just write a wrapper around Task.Result when calling the synchronous method. But how do I not need to duplicate a lot of code between asynchronous methods and synchronization methods, since we want to save both options in the library?

For example, the library currently uses the TextReader.Read method. In terms of asynchronous change, we would like to use the TextReader.ReadAsync method. Since this is the basis of libraries, it seems to me that I will need to duplicate a lot of code between synchronous and asynchronous methods (it is desirable that DRY code be as possible). Or I need to reorganize them in the PreRead and PostRead , which seem to clutter up the code and what TPL is trying to fix.

I am thinking of just wrapping the TextReader.Read method in Task.Return() . Even though this is a task, improvements from TPL should not include it in another thread, and I can still use async, expecting most of the code as usual. Would it be nice if the synchronous wrapper was just Task.Result or Wait() ?

I looked at other examples in the .net library. StreamReader duplicate code between asynchronous and asynchronous. MemoryStream does Task.FromResult .

Also planning everywhere, I could add ConfigureAwait(false) , as it is just a library.

Update:

What I'm talking about duplicate code,

  public decimal ReadDecimal() { do { if (!Read()) { SetInternalProperies() } else { return _reader.AsDecimal(); } } while (_reader.hasValue) } public async Task<decimal> ReadDecimalAsync() { do { if (!await ReadAsync()) { SetInternalProperies() } else { return _reader.AsDecimal(); } } while (_reader.hasValue) } 

This is a small example, but you can see that the only code change is wait and task.

To be clear, I want to use the async / await and TPL code in the library, but I still need to use the old synchronization methods as well. I am not going to just Task.FromResult() use synchronization methods. What I thought was with a flag that says I want the synchronization method and at the root to check the flag in something like

  public decimal ReadDecimal() { return ReadDecimalAsyncInternal(true).Result; } public async Task<decimal> ReadDecimal() { return await ReadDecimalAsyncInternal(false); } private async Task<decimal> ReadDecimalAsyncInternal(bool syncRequest) { do { if (!await ReadAsync(syncRequest)) { SetInternalProperies() } else { return _reader.AsDecimal(); } } while (_reader.hasValue) } private Task<bool> ReadAsync(bool syncRequest) { if(syncRequest) { return Task.FromResult(streamReader.Read()) } else { return StreamReader.ReadAsync(); } } 
+7
c # asynchronous task-parallel-library async-await
source share
2 answers

You want to add asynchronous methods in addition to the synchronous ones in your library. The article you contacted says exactly that. It is recommended that you create custom code for both versions.

Now this advice is usually given because:

  • Asynchronous methods should be low. For efficiency, they must use async IO internally.
  • Synchronization methods should use the IO synchronization function for efficiency reasons.

If you create wrappers, you can mislead callers.

Now, this is a valid strategy for creating wrappers in both directions, if you're fine with the consequences. This certainly saves a lot of code. But you have to decide whether to prefer synchronization or version for asynchronous use. The other will be less efficient and will not have performance-based reasons.

You rarely find it in BCL because the quality of implementation is great. But, for example, the ADO.NET 4.5 SqlConnection class uses sync-over-async. The cost of executing an SQL query is much more than the overhead of synchronization. This is a good use case. MemoryStream uses (view) async-over-sync because it essentially only works with the processor, but it must implement Stream .

What is the overhead actually? Expect you will be able to run> 100 million Task.FromResult per second, and millions of near-zero work Task.Run per second. This is a small overhead compared to many things.

+5
source share

Would it be nice if the synchronous shell was just Task.Result or Wait ()?

You need to understand what asynchronous I / O is. This is not about duplicating code, but about using the fact that you don't need threads when the work is naturally asynchronous.

If you wrap your synchronous code with a task, you will miss this advantage. In addition, you will be misleading your API users when they assume that the expected call will return the call to the caller.

Edit:

Your examples reinforce my point of view. Do not use tasks. Synchronous apis in itself is completely beautiful, don’t force to use TPL in them when it is not needed, eveb if it forces your code base to increase by 2x the number of lines.

Take the time to properly implement the asynchronous api. Do not block asynchronous code, keep it completely at the bottom of the stack.

+1
source share

All Articles