使用管道时一路异步

如果我有一个使用具有许多阶段的管道的应用程序,请在所有阶段上使用foreach并调用:

CanExecute
Execute

接口是这样的

public interface IService
{
    bool CanExecute(IContext subject);

    IContext Execute(IContext subject);
}

基本上,它接受上下文,并返回变得更丰富的上下文。

在阶段Execute方法之一中,我需要调用服务,并且想要进行异步处理。 所以现在Execute方法需要更改为例如

Task<IContext> ExecuteAsync(IContext subject);

使用await来调用该服务。

所有其他阶段都没有异步代码,但是现在需要更改,因为最佳实践是“一直异步”。

引入异步代码时,进行这些更改是否正常?

TJJ123456 回答:使用管道时一路异步

  

引入异步代码时,进行这些更改是否正常?

当您更改任何方法的签名时,通常必须进行更改。如果要重命名并更改返回类型,那么可以,在调用该方法的任何地方都必须更改。

更改它们的最好方法是在整个过程中也使其异步。

,

C#8提供了多种避免修改同步服务的方法。 C#7也可以使用模式匹配语句来解决这个问题。

默认实施成员

接口版本控制是默认接口成员的主要用例之一。它们可以用来避免在接口更改时更改现有的类。您可以为ExecuteAsync添加一个默认实现,该实现将Execute的结果作为ValueTask返回。

假设您具有以下接口:

public interface IContext{}

public interface IService
{
    public bool CanExecute(IContext subject);

    public IContext Execute(IContext subject);       
}

public class ServiceA:IService
{
    public bool CanExecute(IContext subject)=>true;
    public IContext Execute(IContext subject){return subject;}
}

要创建异步服务而无需修改同步服务,可以向IService添加默认实现,并在新服务中覆盖它:

public interface IService
{
    public bool CanExecute(IContext subject);

    public IContext Execute(IContext subject);

    public ValueTask<IContext> ExecuteAsync(IContext subject)=>new ValueTask<IContext>(Execute(subject));

}

public class ServiceB:IService
{
    public bool CanExecute(IContext subject)=>true;
    public IContext Execute(IContext subject)=>ExecuteAsync(subject).Result;

    public async ValueTask<IContext> ExecuteAsync(IContext subject)
    {
        await Task.Yield();
        return subject;
    }
}    

ServiceB.Execute仍然需要一个身体,并且有意义的一件事是调用ExecuteAsync()并阻止,看起来很丑。另一种可能性是如果调用Execute则抛出:

public IContext Execute(IContext subject)=>throw new InvalidOperationException("This is an async service");

模式匹配

另一种选择是为异步服务创建第二个接口:

public interface IService
{
    public bool CanExecute(IContext subject);

    public IContext Execute(IContext subject);        

}

public interface IServiceAsync:IService
{        
    public ValueTask<IContext> ExecuteAsync(IContext subject);    
}

两个服务实现都将保持不变。根据服务的类型,管道代码将更改为进行不同的调用:

async Task Main()
{
    IService[] pipeline=new[]{(IService)new ServiceA(),new ServiceB()};
    IContext ctx=new Context();
    foreach(var svc in pipeline)
    {
        if (svc.CanExecute(ctx))
        {
            var result=svc switch { IServiceAsync a=>await a.ExecuteAsync(ctx),IService b => b.Execute(ctx)};        
            ctx=result;
        }
    }
}

模式匹配表达式根据当前服务的类型调用不同的分支。在某个类型上进行嵌套会生成一个强类型实例(a或b),该实例可用于调用适当的方法。

开关表达式是详尽无遗的-如果编译器无法验证模式是否匹配了所有选项,则会生成警告。

C#7

C#7没有switch表达式,因此需要更详细的模式匹配switch语句:

if (svc.CanExecute(ctx))
{
    switch (svc)
    {
        case IServiceAsync a:
            ctx=await a.ExecuteAsync(ctx);                    
            break;                    
        case IService b :
            ctx=b.Execute(ctx);        
            break;
        default:
            throw new InvalidOperationException("Unknown service type!");
    }
}

切换语句并不详尽,因此我们需要添加default部分以在运行时捕获错误。

本文链接:https://www.f2er.com/3158056.html

大家都在问