使用异步与相同的功能的同步和异步API实现库时等待 [英] Using async await when implementing a library with both synchronous and asynchronous API for the same functionality

查看:136
本文介绍了使用异步与相同的功能的同步和异步API实现库时等待的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有关于如何提供同步和异步执行相同的功能库中的几个问题。我要去先问他们,然后提供的例子code以下(这实际上是相当多的,但实际上它很简单)。

  1. 有没有一种方法,以避免违反DRY原则?考虑 JsonStreamReader.Read JsonStreamWriter.Write JsonStreamWriter.Flush ProtocolMessenger.Send ProtocolMessenger.Receive 及其异步版本。

  2. 有没有一种方法,以避免侵犯时,单元测试相同的方法,同步和异步版本DRY原则?我使用NUnit的,但我想所有的框架应该是在这方面是相同的。

  3. 应该如何实现的方法返回工作任务<东西> 审议以1 ComplexClass.Send 变异>和 ComplexClass.Receive ?哪一个是正确的,为什么?

  4. 是否正确始终包括 .ConfigureAwait(假)等待在图书馆考虑到它不知道那里的图书馆将被使用(控制台应用程序,Windows窗体,WPF,ASP.NET)?

在这里沿用了code我指的是在第一个问题。

IWriter JsonStreamWriter

 公共接口IWriter
{
    无效写(obj对象);
    任务WriteAsync(obj对象);
    无效的flush();
    任务FlushAsync();
}

公共类JsonStreamWriter:IWriter
{
    私人只读流_stream;

    公共JsonStreamWriter(流流)
    {
        _stream =流;
    }

    公共无效写入(obj对象)
    {
        JSON字符串= JsonConvert.SerializeObject(OBJ);
        字节[]字节= Encoding.UTF8.GetBytes(JSON);
        _stream.Write(字节,0,bytes.Length);
    }

    公共异步任务WriteAsync(obj对象)
    {
        JSON字符串= JsonConvert.SerializeObject(OBJ);
        字节[]字节= Encoding.UTF8.GetBytes(JSON);
        等待_stream.WriteAsync(字节,0,bytes.Length).ConfigureAwait(假);
    }

    公共无效的flush()
    {
        _stream.Flush();
    }

    公共异步任务FlushAsync()
    {
        等待_stream.FlushAsync()ConfigureAwait(假)。
    }
}
 

的iReader JsonStreamReader

 公共接口的iReader
{
    阅读对象(类型的objectType);
    任务<对象> ReadAsync(类型的objectType);
}

公共类JsonStreamReader:留园
{
    私人只读流_stream;

    公共JsonStreamReader(流流)
    {
        _stream =流;
    }

    公共对象读取(类型的objectType)
    {
        字节[]字节=新字节[1024];
        INT读取动作= _stream.Read(字节,0,bytes.Length);
        串JSON = Encoding.UTF8.GetString(字节,0,读取动作);
        obj对象= JsonConvert.DeserializeObject(JSON,的objectType);
        返回OBJ;
    }

    公共异步任务<对象> ReadAsync(类型的objectType)
    {
        字节[]字节=新字节[1024];
        INT读取动作=等待_stream.ReadAsync(字节,0,bytes.Length).ConfigureAwait(假);
        串JSON = Encoding.UTF8.GetString(字节,0,读取动作);
        obj对象= JsonConvert.DeserializeObject(JSON,的objectType);
        返回OBJ;
    }
}
 

IMessenger的 ProtocolMessenger

 公共接口IMessenger的
{
    无效发送(对象消息);
    任务SendAsync(对象消息);
    接收对象();
    任务<对象> ReceiveAsync();
}

公共接口IMessageDescriptor
{
    字符串GetMessageName(类型为messageType);
    键入GetMessageType(字符串messageName);
}

公共类头
{
    公共字符串MessageName {获得;组; }
}

公共类ProtocolMessenger:IMessenger的
{
    私人只读IMessageDescriptor _messageDescriptor;
    私人只读IWriter _writer;
    私人只读的iReader _reader;

    公共ProtocolMessenger(IMessageDescriptor messageDescriptor,IWriter作家的iReader阅读器)
    {
        _messageDescriptor = messageDescriptor;
        _writer =作家;
        _reader =阅读器;
    }

    公共无效发送(对象消息)
    {
        标题标题=新的报头();
        header.MessageName = _messageDescriptor.GetMessageName(message.GetType());

        _writer.Write(头);
        _writer.Write(消息);
        _writer.Flush();
    }

    公共异步任务SendAsync(对象消息)
    {
        标题标题=新的报头();
        header.MessageName = _messageDescriptor.GetMessageName(message.GetType());

        等待_writer.WriteAsync(头).ConfigureAwait(假);
        等待_writer.WriteAsync(消息).ConfigureAwait(假);
        等待_writer.FlushAsync()ConfigureAwait(假)。
    }

    公共对象接收()
    {
        标题标题=(头)_reader.Read(typeof运算(头));
        类型为messageType = _messageDescriptor.GetMessageType(header.MessageName);
        对象消息= _reader.Read(为messageType);
        返回消息;
    }

    公共异步任务<对象> ReceiveAsync()
    {
        标题标题=(头)等待_reader.ReadAsync(typeof运算(头))ConfigureAwait(假)。
        类型为messageType = _messageDescriptor.GetMessageType(header.MessageName);
        对象消息=等待_reader.ReadAsync(为messageType).ConfigureAwait(假);
        返回消息;
    }
}
 

ComplexClass

 公共接口ISomeOtherInterface
{
    无效的doSomething();
}

公共类ComplexClass:IMessenger的,ISomeOtherInterface
{
    私人只读IMessenger的_messenger;
    私人只读ISomeOtherInterface _someOtherInterface;

    公共ComplexClass(IMessenger的使者,ISomeOtherInterface someOtherInterface)
    {
        _messenger =使者;
        _someOtherInterface = someOtherInterface;
    }

    公共无效DoSomething的()
    {
        _someOtherInterface.DoSomething();
    }

    公共无效发送(对象消息)
    {
        _messenger.Send(消息);
    }

    //取1
    公共任务SendAsync(对象消息)
    {
        返回_messenger.SendAsync(消息);
    }

    //取2
    公共异步任务SendAsync(对象消息)
    {
        等待_messenger.SendAsync(消息).ConfigureAwait(假);
    }

    公共对象接收()
    {
        返回_messenger.Receive();
    }

    //取1
    公共任务<对象> ReceiveAsync()
    {
        返回_messenger.ReceiveAsync();
    }

    //取2
    公共异步任务<对象> ReceiveAsync()
    {
        返回等待_messenger.ReceiveAsync()ConfigureAwait(假)。
    }
}
 

解决方案

这里的一般的答案是,使得无论真正异步 并同步版本同样的功能需要的 2不同的(也许相似,也许不是)的实施。你可以尝试找到重复的部分,并使用一个基类(或工具类),重用他们,但在实现主要将有所不同。

在许多情况下,人们选择只提供的API的一个版本,它是异步或没有。例如,对于YouTube的API V3 的 .NET客户端库是完全异步一路过关斩将。如果你能负担得起的(许多人不能),这将是我的建议。

关于你的具体问题:

  1. 不是真的,不是寻找类似的部件和抽象他们离开等。
  2. 不是真的,同步方法需要在同步方面,而异步环境测试异步的。
  3. 以1 (即直接返回一个任务)是preferable 2种方式:
    • 在它缺乏创建整个不必要的异步状态机增加了一个的很轻微的性能提升的开销。
    • ConfigureAwait 在这种情况下,仅影响code表示它之后,在这种情况下是没有。它不会影响来电的code是否使用 ConfigureAwait 还是不行。
  4. 肯定是有的(最后阳性)。 异步 code的库应该使用 ConfigureAwait(假)默认情况下,并删除它,只有当有一个特定的需要。

I've got a few questions about how to provide both synchronous and asynchronous implementation of the same functionality in a library. I am gonna ask them first and then provide the example code below (which is actually quite a bit, but in fact it's quite simple).

  1. Is there an approach to avoid violating the DRY principle? Consider the implementations of JsonStreamReader.Read, JsonStreamWriter.Write, JsonStreamWriter.Flush, ProtocolMessenger.Send, ProtocolMessenger.Receive and their asynchronous versions.

  2. Is there an approach to avoid violating the DRY principle when unit testing both synchronous and asynchronous versions of the same method? I am using NUnit, although I guess all frameworks should be the same in this regard.

  3. How should be implemented a method returning Task or Task<Something> considering the Take 1 and Take 2 variants of ComplexClass.Send and ComplexClass.Receive? Which one is correct and why?

  4. Is it correct to always include .ConfigureAwait(false) after await in a library considering it is not known where the library will be used (Console application, Windows Forms, WPF, ASP.NET)?

And here follows the code I am referring to in the first questions.

IWriter and JsonStreamWriter:

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);
    }
}

IReader and JsonStreamReader:

public interface IReader
{
    object Read(Type objectType);
    Task<object> 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<object> 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;
    }
}

IMessenger and ProtocolMessenger:

public interface IMessenger
{
    void Send(object message);
    Task SendAsync(object message);
    object Receive();
    Task<object> 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<object> 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<object> ReceiveAsync()
    {
        return _messenger.ReceiveAsync();
    }

    // Take 2
    public async Task<object> ReceiveAsync()
    {
        return await _messenger.ReceiveAsync().ConfigureAwait(false);
    }
}

解决方案

The general answer here is that making both truly async and sync versions of the same functionality requires 2 different (maybe similar, maybe not) implementations. You can try and find duplicate parts and reuse them using a base class (or a utility class) but the implementations would mostly be different.

In many cases, people choose to only supply one version of the API, be it asynchronous or not. For example the .Net client library for YouTube API v3 is entirely async all the way through. If you can afford that (many can't) that would be my recommendation.

About your specific questions:

  1. Not really, other than finding similar parts and abstracting them away.
  2. Not really, synchronous methods need to be tested in a synchronous context while async ones in an async context.
  3. Take 1 (i.e. returning a task directly) is preferable in 2 ways:
    • It lacks the overhead of creating the whole unneeded async state machine which adds a very slight performance boost.
    • ConfigureAwait in this case affects only the code that comes after it, which in this case is none at all. It doesn't affect the caller's code whether it uses ConfigureAwait or not.
  4. Definitely yes (finally, positivity). async code in libraries should use ConfigureAwait(false) by default, and remove it only when there's a specific need to.

这篇关于使用异步与相同的功能的同步和异步API实现库时等待的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆