Обеспечение синхронизации и асинхронных методов - PullRequest
0 голосов
/ 23 октября 2019

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

Существует множество вопросов, связанных с вызовом асинхронизации из синхронизации иразличные трудности развертывания исключений и возврата к правильному контексту выполнения, но я не могу найти никаких вопросов относительно того, как позволить потребляющим разработчикам выбирать между асинхронным или синхронизированным путем выполнения.

Давайте рассмотрим два возможных шаблона разработки, которые я мог бы использовать для обеспечения синхронизации и асинхронного метода для одной и той же работы.

public interface IMyService
{
    MyResponse DoWork(MyRequest req);
    Task<MyResponse> DoWorkAsync(MyRequest req, CancellationToken token);
}

public class MyServiceSyncWrapped : IMyService
{
    public MyResponse DoWork(MyRequest req)
    {
        //Do the actual work, but sync-d
    }

    //No need to make this async/await, here it's pure overhead
    public Task<MyResponse> DoWorkAsync(MyRequest req, CancellationToken token)
    {
        //Return DoWork wrapped in a task
    }
}

public class MyServiceAsyncWrapped : IMyService
{
    public MyResponse DoWork(MyRequest req)
    {
        //Return DoWorkAsync, unwrapped!
    }

    //async/await actually potentially adds value here
    public async Task<MyResponse> DoWorkAsync(MyRequest req, CancellationToken token)
    {
        //Do the actual work, fully async-ly
    }
}

Есть ли какое-либо существенное значение в обеспечении обеих синхронизаций? и асинхронные методы (или это анти-шаблон)? Если это считается желательным и с учетом того, насколько сложно развернуть асинхронный код по сравнению с тем, насколько лучше использовать асинхронное полное прохождение кодовой базы, что определяет ценность для каждого подхода (или однозначно предпочтительнее)? Должен ли я просто отказаться от методов синхронизации и заставить других разработчиков разрабатывать для async?

Ответы [ 2 ]

2 голосов
/ 23 октября 2019

Шаблон, который использует Microsoft, заключается в том, что синхронные версии их методов являются совершенно другими реализациями, чем их асинхронные версии. Синхронные версии - это не просто обертки вокруг асинхронных версий.

Вы можете увидеть это в их исходном коде. Например, сравните File.InternalReadAllText() (который используется File.ReadAllText()) с File.InternalReadAllTextAsync() (который используется File.ReadAllTextAsync()).

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

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

1 голос
/ 23 октября 2019

Писать асинхронную версию процедуры имеет смысл только в том случае, если в версии синхронизации имеется ожидание в режиме ожидания, обычно ожидание завершения другого процесса, например, ожидание открытия файла или результатов запроса к базе данных. или некоторая информация из Интернета.

Метод синхронизации просто бездействует, ожидая завершения другого процесса, блокируя, таким образом, выполнение вашей программы. В методе async-await поток просматривает, может ли он сделать что-то еще вместо ожидания ожидания.

Это легче всего понять, если сравнить это с поваром, готовящим завтрак, как описано в это интервью с Эриком Липпертом . Найдите где-то посередине асинхронное ожидание.

Если повар должен делать завтрак синхронно, он начнет кипятить воду, подождать, пока вода не закипит, использовать кипящую воду, чтобы приготовить чай, начать поджаривать хлеб, подождать, покахлеб поджаривают, начинают варить яйца, ждут, пока яйца не закипят, и т. д. Вы увидите все ожидания ожидания.

Асинхронный повар начнет кипятить воду, но вместо того, чтобы ждатьвода до кипения, он начал бы поджаривать хлеб и кипящую воду. Хотя ему приходится ждать завершения этих процессов, он может вместо этого делать что-то еще, например, нарезать помидоры. Через некоторое время вода для чая закипела. Он заваривает чай и продолжает работать, пока хлеб не будет поджарен и т. Д. Пока есть работа, повар не будет бездельничать, а будет делать то, что может, без результатов заданий, которые он начал.

Вернуться к вашему вопросу

Если процедуре не нужно ждать завершения какого-либо другого процесса, создавать для нее асинхронную версию не имеет смысла. Если процедура занимает значительное время, позвольте вашему абоненту решить, нужно ли ему реагировать, и запустите задачу, которая вызывает вашу функцию.

Другая причина - реализация интерфейса. Это может быть причиной не создания одного интерфейса с версиями sync и async, а двумя интерфейсами: один с функциями синхронизации и один с функциями асинхронности. Классы с функциями, которым не нужно ждать без дела, будут реализовывать только синхронизированную версию интерфейса.

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

IEnumerable<int> ReadNumbers(string fileName1)
{
    // Read File 1, wait idly,
    string text1 = ReadTextFile(fileName1);

    // convert the read texts to numbers, and return in ascending order
    IEnumerable<int> result = ConvertToAscendingInt(text1);
    return result;
}

Этот метод ожидает в режиме ожидания при чтении текстового файла. Чтобы предотвратить это, вызовите асинхронную версию. Все остальные вещи не являются бездействующими, нет необходимости в асинхронной версии:

async Task<IEnumerable<int>> ReadNumbersAsync(string fileName1)
{
    // Start reading File 1 async
    string text1 = await ReadTextFileAsync(fileName1);

    // convert the read texts to numbers, and return in ascending order
    IEnumerable<int> result = ConvertToAscendingInt(text1);
    return result;
}

Таким образом, большая часть кода будет в подметодах. Легче видеть, что методы sync и async делают одно и то же. Если в будущем преобразование будет выполнено по-другому, оно будет выполнено автоматически как для синхронизации, так и для асинхронной версии.

Наконец, вы можете разрешить версии синхронизации вызывать асинхронную версию. Затраты на выполнение задачи замедляют синхронизацию:

IEnumerable<int> ReadNumbers(string fileName1)
{
   var taskReadNumbers = Task.Run( () => ReadNumbersAsync(fileName1);
   taskReadNumbers.Wait();
   return taskReadNumbers.Result();
}

Для простоты я исключил обработку исключений

...