C# 使用管道时始终异步

C# 使用管道时始终异步,c#,async-await,task-parallel-library,default-interface-member,switch-expression,C#,Async Await,Task Parallel Library,Default Interface Member,Switch Expression,如果我有一个应用程序,它使用一个管道,在所有阶段上使用foreach执行多个阶段,并调用: CanExecute 执行 接口是这样的: 公共接口iSeries设备 { bool CanExecute(IContext主题); IContext执行(IContext主题); } 它基本上接受一个上下文并返回一个它变得更丰富的上下文 在其中一个stageExecute方法中,我需要调用一个服务,并希望执行异步。 因此,现在需要将Execute方法更改为 任务执行同步(IContext主题);

如果我有一个应用程序,它使用一个管道,在所有阶段上使用
foreach
执行多个阶段,并调用:

CanExecute
执行
接口是这样的:

公共接口iSeries设备
{
bool CanExecute(IContext主题);
IContext执行(IContext主题);
}
它基本上接受一个上下文并返回一个它变得更丰富的上下文

在其中一个stage
Execute
方法中,我需要调用一个服务,并希望执行异步。 因此,现在需要将
Execute
方法更改为

任务执行同步(IContext主题);
使用
等待
呼叫服务

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

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

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

当您更改任何方法的签名时,必须进行更改是正常的。如果您想重命名它并更改返回类型,那么是的,调用该方法的任何地方都必须更改

改变它们的最好方法是使它们在整个链条上都是异步的。

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

要在不修改同步服务的情况下创建异步服务,可以向iSeries设备添加默认实现,并在新服务中覆盖它:

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

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

    public IContext Execute(IContext subject);        

}

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

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

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

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

    public IContext Execute(IContext subject);        

}

public interface IServiceAsync:IService
{        
    public ValueTask<IContext> ExecuteAsync(IContext subject);    
}
模式匹配表达式根据当前服务的类型调用不同的分支。对类型进行Natching会生成一个强类型实例(a或b),可用于调用适当的方法

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

C#7

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

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

Switch语句并非详尽无遗,因此我们需要添加
default
部分来捕获运行时的错误。

是的,我看到了
Task。FromResult
在所有实现异步方法的方法中使用了很多,但实际上是同步的。请使用
ValueTask
而不是
Task
,以避免为这些同步操作分配新对象。如果使用C#8,则默认情况下可以使用默认接口成员返回同步结果,并且仅覆盖
async
方法,例如
public-ValueTask-ExecuteAsync(IContex-context)=>new ValueTask(Execute(context))Panagiotis Kanavos感谢您的回复,使用c#7作为8,目前无法使用