Асинхронизация полностью при использовании конвейера - PullRequest
0 голосов
/ 05 ноября 2019

Если у меня есть приложение, которое использует конвейер со многими этапами, которые должны быть выполнены с использованием foreach на всех этапах и вызовите:

CanExecute
Execute

Интерфейс такой:

public interface IService
{
    bool CanExecute(IContext subject);

    IContext Execute(IContext subject);
}

Он в основном берет контекст и возвращает контекст, в котором он стал богаче.

В рамках одного из этапов Execute метод мне нужно вызвать сервис и хочу сделать асинхронным. Таким образом, теперь метод Execute должен измениться, например, на

Task<IContext> ExecuteAsync(IContext subject);

с await для вызова службы.

Все остальные этапы не имеют асинхронного кода, но должны быть измененыТеперь, поскольку наилучшей практикой является «асинхронный полностью».

Нормально ли это делать при внесении асинхронного кода?

Ответы [ 2 ]

0 голосов
/ 05 ноября 2019

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

Pattern Matching

Другой вариант - создать второй интерфейс только для асинхронных служб:

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, чтобы перехватывать ошибки во время выполнения.

0 голосов
/ 05 ноября 2019

Нормально ли вносить эти изменения при вводе асинхронного кода?

Нормально вносить изменения при изменении сигнатуры любого метода. Если вы хотите переименовать его и изменить тип возвращаемого значения, тогда да, везде, где вызывается этот метод, придется менять.

Лучший способ изменить их - это сделать их асинхронными также по всей цепочке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...