如何为有时仅异步的操作创建和实现接口 [英] How to create and implement interfaces for operations that are only sometimes async

查看:91
本文介绍了如何为有时仅异步的操作创建和实现接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

比方说,我有100多个使用计算"​​方法实现通用接口的类.一些类将执行异步(例如,读取文件),而其他实现相同接口的类将执行同步的代码(例如,添加两个数字).为维护和性能而编写此代码的好方法是什么?

Let's say I have 100s of classes that implement a common interface with a method "calculate". Some of the classes will execute async (e.g. read a file), and other classes implementing the same interface will execute code that is sync (e.g. adding two numbers). What is a good way to code this, for maintenance and for performance?

到目前为止,我所阅读的帖子始终建议使async/await方法冒泡给调用者.因此,如果您有一个异步操作,则使调用方异步,然后使其调用方异步,依此类推.因此,这使我认为该接口应该是异步接口.但是,这在使用同步代码实现接口时会产生问题.

The posts I read so far, always recommend to make async/await methods bubble up to the callers. So if you have one operation that is async, make the caller async, then its caller async, and so on. So this makes me think that the interface should be an async interface. However, this creates a problem when implementing the interface with code that is synchronous.

我想到的一个主意是在接口中公开2个方法,一个异步和一个同步,以及一个布尔属性,以告知调用方要调用的方法.不过,这看起来真的很丑.

One idea I thought of is to expose in the interface 2 methods, one async and one sync, and one boolean property to tell the caller which method to call. This would look really ugly though.

我当前拥有的代码只是一个异步的接口方法.然后,对于同步的实现,它们将代码包装在Task对象中:

The code I currently have is only one interface method that is async. Then for implementations that are synchronous, they wrap the code inside a Task object:

using System.IO;
using System.Threading.Tasks;

namespace TestApp
{
    interface IBlackBox
    {
        Task<string> PullText();
    }

    sealed class MyAsyncBlackBox : IBlackBox
    {
        public async Task<string> PullText()
        {
            using (var reader = File.OpenText("Words.txt"))
            {
                return await reader.ReadToEndAsync();
            }
        }
    }

    sealed class MyCachedBlackBox : IBlackBox
    {
        public Task<string> PullText()
        {
            return Task.Run(() => "hello world");
        }
    }
}

这是创建和实现仅有时是异步的接口的正确方法吗?我有很多实现短同步操作的类,并担心这会增加很多开销.还有其他我想念的方式吗?

Is this the right approach to create and implement an interface that is only sometimes async? I have a lot of classes that implement short synchronous operations, and worry that this could add a lot of overhead. Is there some other way to do this that I am missing?

推荐答案

这是接口的常见情况...如果您有需要为异步等待模式,我们必须在接口中实现该Task.

This is a common situation with interfaces... If you have a contract that needs to specify a task for the Async Await Pattern and we have to implement that Task in the interface.

假设呼叫者要使用await,您可以放下async并返回Task.

Assuming the caller is going to use await you can just drop the async and return a Task.

但是,您需要注意自己的例外.它假定 exceptions 被放置在 task 上.因此,为了保持这种状态,呼叫者将期望您对它们的处理稍有不同.

However, you need to be-careful with your exceptions. Its assumed that exceptions are placed on the task. So to keep this plumbing the caller will expect you have to handle them slightly differently.

常用用法

标准async

public async Task<string> PullText()
{
   using (var reader = File.OpenText("Words.txt"))
   {
      return await reader.ReadToEndAsync();
   }
}

CPU绑定工作返回Task(捕获异常并将其放置在Task上)

Returning a Task for CPU bound work (capturing the exception and placing it on the Task)

public Task<string> PullText()
{
   try
   {
      return Task.Run(() => DoCpuWork());
   }
   catch (Exception e)
   {
      return Task.FromException<string>(e);
   }
}

由于我们检测IAsyncStateMachine

public async Task<string> PullText()
{
    return await Task.Run(() => DoCpuWork());
}

以简单的结果返回完成的 Task(捕获异常并将其放置在Task上)

Returning a completed Task with a simple results (capturing the exception and placing it on the Task)

public Task<string> PullText()
{
   try
   {
      // simplified example
      return Task.FromResult("someString");
   }
   catch (Exception e)
   {
      return Task.FromException<string>(e);
   }
}

还有第三种方法,您可以使用async关键字,并pragma发出警告,这将为您处理错误语义.这对我来说有点脏,只是因为它看起来很凌乱并且需要pragma发出警告,尽管我现在已经看到了在定制生产库中使用的

There is also a 3rd approach, you can use the async keyword, and pragma out the warnings, this takes care of the error semantics for you. This feels a little dirty to me, just because it looks messy and the need to pragma out the warning, though i have now seen this used in bespoke production libraries

#pragma warning disable 1998
public async Task<string> PullText()()
#pragma warning restore 1998
{
    return Task.Run(() => "hello world");
}

#pragma warning disable 1998
public async Task<string> PullText()()
#pragma warning restore 1998
{
    return Task.FromResult("someString");
}

注意以上所有内容都与从方法中返回Task<T>有关.如果只是想返回Task,则可以利用具有与上述相同的错误语义的Task.CompletedTask;.

Note all the above deal with returning a Task<T> from the method. If one was just wanting to return the Task you can take advantage of Task.CompletedTask; with the same error semantics as above.

这篇关于如何为有时仅异步的操作创建和实现接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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