使用异步等待实现具有同步和异步API的库以实现相同的function

我有一些关于如何在库中提供相同function的同步和异步实现的问题。 我先问他们然后提供下面的示例代码(实际上相当多,但事实上它很简单)。

  1. 有没有办法避免违反DRY原则? 考虑JsonStreamReader.ReadJsonStreamWriter.WriteJsonStreamWriter.FlushProtocolMessenger.SendProtocolMessenger.Receive及其异步版本的实现。

  2. 在对同一方法的同步和异步版本进行unit testing时,是否存在避免违反DRY原则的方法? 我正在使用NUnit,虽然我猜所有框架在这方面应该是相同的。

  3. 应该如何实现一个返回TaskTask考虑ComplexClass.SendComplexClass.ReceiveTake 1Take 2变体? 哪一个是正确的,为什么?

  4. 考虑到不知道库将在何处使用(控制台应用程序,Windows窗体,WPF,ASP.NET),在库中await之后总是包含.ConfigureAwait(false)是否正确?

以下是我在第一个问题中提到的代码。

IWriterJsonStreamWriter

 public interface IWriter { void Write(object obj); Task WriteAsync(object obj); void Flush(); Task FlushAsync(); } public class JsonStreamWriter : IWriter { private readonly Stream _stream; public JsonStreamWriter(Stream stream) { _stream = stream; } public void Write(object obj) { string json = JsonConvert.SerializeObject(obj); byte[] bytes = Encoding.UTF8.GetBytes(json); _stream.Write(bytes, 0, bytes.Length); } public async Task WriteAsync(object obj) { string json = JsonConvert.SerializeObject(obj); byte[] bytes = Encoding.UTF8.GetBytes(json); await _stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); } public void Flush() { _stream.Flush(); } public async Task FlushAsync() { await _stream.FlushAsync().ConfigureAwait(false); } } 

IReaderJsonStreamReader

 public interface IReader { object Read(Type objectType); Task ReadAsync(Type objectType); } public class JsonStreamReader : IReader { private readonly Stream _stream; public JsonStreamReader(Stream stream) { _stream = stream; } public object Read(Type objectType) { byte[] bytes = new byte[1024]; int bytesRead = _stream.Read(bytes, 0, bytes.Length); string json = Encoding.UTF8.GetString(bytes, 0, bytesRead); object obj = JsonConvert.DeserializeObject(json, objectType); return obj; } public async Task ReadAsync(Type objectType) { byte[] bytes = new byte[1024]; int bytesRead = await _stream.ReadAsync(bytes, 0, bytes.Length).ConfigureAwait(false); string json = Encoding.UTF8.GetString(bytes, 0, bytesRead); object obj = JsonConvert.DeserializeObject(json, objectType); return obj; } } 

IMessengerProtocolMessenger

 public interface IMessenger { void Send(object message); Task SendAsync(object message); object Receive(); Task ReceiveAsync(); } public interface IMessageDescriptor { string GetMessageName(Type messageType); Type GetMessageType(string messageName); } public class Header { public string MessageName { get; set; } } public class ProtocolMessenger : IMessenger { private readonly IMessageDescriptor _messageDescriptor; private readonly IWriter _writer; private readonly IReader _reader; public ProtocolMessenger(IMessageDescriptor messageDescriptor, IWriter writer, IReader reader) { _messageDescriptor = messageDescriptor; _writer = writer; _reader = reader; } public void Send(object message) { Header header = new Header(); header.MessageName = _messageDescriptor.GetMessageName(message.GetType()); _writer.Write(header); _writer.Write(message); _writer.Flush(); } public async Task SendAsync(object message) { Header header = new Header(); header.MessageName = _messageDescriptor.GetMessageName(message.GetType()); await _writer.WriteAsync(header).ConfigureAwait(false); await _writer.WriteAsync(message).ConfigureAwait(false); await _writer.FlushAsync().ConfigureAwait(false); } public object Receive() { Header header = (Header)_reader.Read(typeof(Header)); Type messageType = _messageDescriptor.GetMessageType(header.MessageName); object message = _reader.Read(messageType); return message; } public async Task ReceiveAsync() { Header header = (Header)await _reader.ReadAsync(typeof(Header)).ConfigureAwait(false); Type messageType = _messageDescriptor.GetMessageType(header.MessageName); object message = await _reader.ReadAsync(messageType).ConfigureAwait(false); return message; } } 

ComplexClass

 public interface ISomeOtherInterface { void DoSomething(); } public class ComplexClass : IMessenger, ISomeOtherInterface { private readonly IMessenger _messenger; private readonly ISomeOtherInterface _someOtherInterface; public ComplexClass(IMessenger messenger, ISomeOtherInterface someOtherInterface) { _messenger = messenger; _someOtherInterface = someOtherInterface; } public void DoSomething() { _someOtherInterface.DoSomething(); } public void Send(object message) { _messenger.Send(message); } // Take 1 public Task SendAsync(object message) { return _messenger.SendAsync(message); } // Take 2 public async Task SendAsync(object message) { await _messenger.SendAsync(message).ConfigureAwait(false); } public object Receive() { return _messenger.Receive(); } // Take 1 public Task ReceiveAsync() { return _messenger.ReceiveAsync(); } // Take 2 public async Task ReceiveAsync() { return await _messenger.ReceiveAsync().ConfigureAwait(false); } } 

这里的一般答案是,制作相同function的真正async和同步版本需要2个不同(可能相似,可能不是)的实现 。 您可以尝试查找重复的部分并使用基类(或实用程序类)重用它们,但实现方式大多不同。

在许多情况下,人们选择只提供一个版本的API,无论是否异步。 例如, 用于YouTube API v3的.Net客户端库完全是async的。 如果你能负担得起(许多人不能)那将是我的建议。

关于您的具体问题:

  1. 不是真的,除了找到相似的部分并将它们抽象出去。
  2. 实际上,同步方法需要在同步上下文中进行测试,而在async上下文中则需要async方法。
  3. Take 1 (即直接返回任务)最好有两种方式:
    • 它缺乏创建整个不需要的async状态机的开销,这增加了非常轻微的性能提升。
    • 在这种情况下, ConfigureAwait仅影响它之后的代码,在这种情况下,它根本不影响。 它是否使用ConfigureAwait不会影响调用者的代码。
  4. 肯定是(最后,积极性)。 库中的async代码默认情况下应使用ConfigureAwait(false) ,并且仅在特定需要时才删除它。

一般来说,API应该是异步的或同步的。 例如,如果您的实现包含I / O,则它应该是异步的。

也就是说,在某些情况下,您确实希望同时拥有同步和异步API。 例如,如果工作自然是异步的,但需要保持同步API以便向后兼容。

如果您处于这种情况,我建议使用boolean参数hack来最小化重复代码的数量。 异步方法上的异步包装和异步方法上的同步包装都是反模式。