我正在修改库以添加异步方法.从 Should I expose synchronous wrappers for asynchronous methods?开始,它指出我不应该在调用同步方法时在Task.Result周围编写一个包装器.但是,我如何在异步方法和同步方法之间复制大量代码,因为我们希望在库中保留两个选项?
例如,库当前使用TextReader.Read方法.部分异步更改我们想使用TextReader.ReadAsync方法.由于这是库的核心,我似乎需要在同步和异步方法之间复制大量代码(希望尽可能保持代码DRY).或者我需要在PreRead和PostRead方法中重构它们,这似乎使代码混乱,以及TPL试图解决的问题.
我正在考虑将TextReader.Read方法包装在Task.Return()中.即使它是一项任务,TPL的改进也不应该让它切换到不同的线程,我仍然可以使用异步等待大多数代码,就像正常一样.那么同步的包装器可以只是Task.Result还是Wait()呢?
我查看了.net库中的其他示例. StreamReader似乎复制了异步和非异步之间的代码. MemoryStream执行Task.FromResult.
还计划到处都可以添加ConfigureAwait(false),因为它只是一个库.
更新:
我所说的重复代码是
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) }
这是一个小例子,但您可以看到唯一的代码更改是等待和任务.
为了说清楚我想在库中的所有位置使用async / await和TPL进行编码,但我仍然需要使用旧的同步方法.我不仅仅是Task.FromResult()的同步方法.我想的是有一个标志,说我想要同步方法,并在根检查标志的东西
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(); } }
解决方法
现在通常会给出建议,因为:
>异步方法应该是低延迟.为了提高效率,他们应该在内部使用异步IO.
>出于效率原因,同步方法应在内部使用同步IO.
如果您创建包装器,可能会误导调用者.
现在,如果你对后果感到满意,那么就可以通过两种方式创建包装器.它当然可以节省大量代码.但您必须决定是优先同步还是异步版本.另一个效率较低,没有基于性能的理由存在.
你很少在BCL中找到这个,因为实施的质量很高.但是例如ADO.NET 4.5的sqlConnection类使用sync-over-async.执行sql调用的成本远远高于同步开销.这是一个好的用例. MemoryStream使用(种类)async-over-sync,因为它本质上只是cpu工作,但它必须实现Stream.
实际上是什么开销?期望能够每秒运行> 1亿个Task.FromResult,并且每秒运行数百万个几乎为零的Task.Run.与许多事情相比,这是一个很小的开销.