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

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

问题描述

关于如何在库中提供相同功能的同步和异步实现,我有几个问题.我会先问他们,然后提供下面的示例代码(实际上有点,但实际上很简单).

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. 有没有办法避免违反 DRY 原则?考虑JsonStreamReader.ReadJsonStreamWriter.WriteJsonStreamWriter.FlushProtocolMessenger.Send的实现>ProtocolMessenger.Receive 及其异步版本.

  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.

在对同一方法的同步和异步版本进行单元测试时,是否有办法避免违反 DRY 原则?我正在使用 NUnit,尽管我想所有框架在这方面都应该是相同的.

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.

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

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?

在库中的 await 之后总是包含 .ConfigureAwait(false) 是否正确,因为不知道库将在哪里使用(控制台应用程序、Windows 窗体、WPF、ASP.NET)?

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.

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

IMessengerProtocolMessenger:

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

推荐答案

这里的一般答案是,使同一功能的真正async和同步版本需要<强>2 种不同(可能相似,也可能不相同)的实现.您可以尝试找到重复的部分并使用基类(或实用程序类)重用它们,但实现大多不同.

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.

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

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.

  1. 不是真的,除了找到相似的部分并将它们抽象出来.
  2. 并非如此,同步方法需要在同步上下文中进行测试,而 async 需要在 async 上下文中进行测试.
  3. Take 1(即直接返回一个任务)有两种方式:
    • 它没有创建整个不需要的async 状态机的开销,这增加了非常轻微的性能提升.
    • ConfigureAwait 在这种情况下只影响它后面的代码,在这种情况下根本不影响.无论是否使用ConfigureAwait,它都不会影响调用者的代码.
  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.

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

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