Как распараллеливание работает на async / await? - PullRequest
0 голосов
/ 01 июля 2019

У меня есть следующий код, который я собираюсь запустить асинхронно. Моя цель состоит в том, чтобы GetPictureForEmployeeAsync() вызывался параллельно столько раз, сколько необходимо. Я хотел бы убедиться, что «ожидание» на CreatePicture не мешает мне сделать это.

    public Task<Picture[]> GetPictures(IDictionary<string, string> tags)
    {
        var query = documentRepository.GetRepositoryQuery();

        var employees = query.Where(doc => doc.Gender == tags["gender"]);

        return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
    }

    private Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
    {
        var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
        var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
        return CreatePicture(tags, base64PictureTask, documentTask);
    }

    private static async Task<Picture> CreatePicture(IDictionary<string, string> tags, Task<string> base64PictureTask, Task<Employee> documentTask)
    {
        var document = await documentTask;

        return new Picture
        {
            EmployeeID = document.ID,
            Data = await base64PictureTask,
            ID = document.ID.ToString(),
            Tags = tags,
        };
    }

Если я правильно понимаю, на Task.WhenAll() не влияют две ожидаемые задачи внутри CreatePicture(), поскольку GetPictureForEmployeeAsync() не ожидается. Я прав насчет этого? Если нет, то как мне реструктурировать код для достижения того, чего я хочу?

Ответы [ 2 ]

3 голосов
/ 01 июля 2019

Я бы хотел убедиться, что «ожидание» на CreatePicture не мешает мне сделать это.

Это не так.

Если я правильно понимаю, Task.WhenAll () не затронуты двумя ожидаемыми задачами внутри CreatePicture (), потому что GetPictureForEmployeeAsync () не ожидается. Я прав насчет этого?

Да и нет. WhenAll никоим образом не ограничен ожидаемыми задачами в CreatePicture, но это не имеет никакого отношения к тому, ожидается ли GetPictureForEmployeeAsync или нет. Эти две строки кода эквивалентны с точки зрения поведения:

return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));
return Task.WhenAll(employees.Select(async employee => await GetPictureForEmployeeAsync(employee, tags)));

Я рекомендую прочитать мое async intro , чтобы лучше понять, как async и await работают с задачами.

Кроме того, поскольку GetPictures имеет нетривиальную логику (GetRepositoryQuery и оценку tags["gender"]), я рекомендую использовать async и await для GetPictures, как таковые:

public async Task<Picture[]> GetPictures(IDictionary<string, string> tags)
{
  var query = documentRepository.GetRepositoryQuery();
  var employees = query.Where(doc => doc.Gender == tags["gender"]);
  var tasks = employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)).ToList();

  return await Task.WhenAll(tasks);
}

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

async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
{
  var base64PictureTask = blobRepository.GetBase64PictureAsync(employee.ID.ToString());
  var documentTask = documentRepository.GetItemAsync(employee.ID.ToString());
  await Task.WhenAll(base64PictureTask, documentTask);
  return CreatePicture(tags, await base64PictureTask, await documentTask);
}

static Picture CreatePicture(IDictionary<string, string> tags, string base64Picture, Employee document)
{
  return new Picture
  {
    EmployeeID = document.ID,
    Data = base64Picture,
    ID = document.ID.ToString(),
    Tags = tags,
  };
}
1 голос
/ 01 июля 2019

При вызове асинхронного метода следует помнить, что как только в этом методе достигается оператор await, элемент управления немедленно возвращается к коду, который вызвал асинхронный метод - - независимо от того, где в методе находится оператор await. При «нормальном» методе управление не возвращается к коду, который вызывает этот метод, пока не будет достигнут конец этого метода.

Итак, в вашем случае вы можете сделать следующее:

private async Task<Picture> GetPictureForEmployeeAsync(Employee employee, IDictionary<string, string> tags)
    {
        // As soon as we get here, control immediately goes back to the GetPictures
        //   method -- no need to store the task in a variable and await it within
        //   CreatePicture as you were doing
        var picture = await blobRepository.GetBase64PictureAsync(employee.ID.ToString());
        var document = await documentRepository.GetItemAsync(employee.ID.ToString());
        return CreatePicture(tags, picture, document);
    }

Поскольку первая строка кода в GetPictureForEmployeeAsync имеет await, элемент управления немедленно вернется обратно к этой строке ...

return Task.WhenAll(employees.Select(employee => GetPictureForEmployeeAsync(employee, tags)));

... как только это будет вызвано. Это будет влиять на то, что все элементы сотрудника будут обрабатываться параллельно (ну, вроде - количество потоков, которые будут выделены вашему приложению, будет ограничено).

В качестве дополнительного совета, если это приложение обращается к базе данных или веб-службе для получения изображений или документов, этот код может вызвать проблемы с исчерпанием доступных подключений. Если это так, рассмотрите возможность использования System.Threading.Tasks.Parallel и установки максимальной степени параллелизма или используйте SemaphoreSlim для управления количеством соединений, используемых одновременно.

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