Как мы можем управлять асинхронными вызовами в Parallel.ForEach? - PullRequest
0 голосов
/ 24 сентября 2019

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

Parallel.ForEach(products, item =>{
 GetProductsInfo(item);
});



public async Task<Product> GetProductsInfo(Product product)
{
    var restClientProduct = new RestClient("URL");
    var restRequestProduct = new RestRequest(Method.POST);
    var proudctRequestJson = JsonConvert.SerializeObject(new ProudctRequest()
    {
        product_code = product.product_code,

    });
    restRequestProduct.AddHeader("cache-control", "no-cache");
    restRequestProduct.AddHeader("Content-Length", proudctRequestJson.Count().ToString());
    restRequestProduct.AddHeader("Content-Type", "application/json");
    restRequestProduct.AddHeader("Accept", "application/json");
    restRequestProduct.AddParameter("undefined", proudctRequestJson, ParameterType.RequestBody);
    var responseProduct = GetResponseContentAsync(restClientProduct, restRequestProduct).Result;
    if (responseProduct.StatusCode == HttpStatusCode.OK)
    {
        // set values form the responseProduct to the product
    }
    return product;
}

private Task<IRestResponse> GetResponseContentAsync(RestClient theClient, RestRequest theRequest)
    {
        var tcs = new TaskCompletionSource<IRestResponse>();
        theClient.ExecuteAsync(theRequest, response =>
        {
            tcs.SetResult(response);
        });
        return tcs.Task;
    }

1 Ответ

2 голосов
/ 24 сентября 2019

Части вашего кода, которые вы нам показали, не работают асинхронно.Вы звоните .Result на GetResponseContentAsync(), который заблокирует поток, пока он не завершит .Это означает, что к моменту завершения Parallel.ForEach все запросы HTTP будут завершены.

Если вы используете await где-то в этом блоке кода, который вы заменили на

// set values form the responseProduct to the product

тогда возможно, что результаты не будут сообщены до окончания Parallel.ForEach.Это потому, что Parallel.ForEach не поддерживает асинхронный код, поэтому он не будет ждать их завершения.

Предположим, что GetProductsInfo на самом деле работает асинхронно

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

  1. Реализуйте свой собственный ForEachAsync.Это было запрошено и, вероятно, будет добавлено (по крайней мере, в .NET Core).Но на самом деле есть пример реализации в проблема, где это было запрошено :
/// <summary>
///     Executes a foreach asynchronously.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="dop">The degrees of parallelism.</param>
/// <param name="body">The body.</param>
/// <returns></returns>
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
    return Task.WhenAll(
        from partition in System.Collections.Concurrent.Partitioner.Create(source).GetPartitions(dop)
        select Task.Run(async delegate
        {
            using (partition)
            {
                while (partition.MoveNext())
                    await body(partition.Current);
            }
        }));
}

Это написано как метод расширения, поэтому вы должны использовать его следующим образом:

await products.ForEachAsync(10, GetProductsInfo);

Где 10 - номер запроса, который вы хотите выполнить за раз.

Вы можете использовать что-то вроде:
Task.WaitAll(items.Select(i => GetProductsInfo(i));

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

await Task.WhenAll(items.Select(i => GetProductsInfo(i))

Однако оба этих метода будут запускать всех запросов одновременно.Если вы знаете, что у вас будет только небольшое число, тогда это нормально.Но если у вас может быть очень большое количество, вы можете залить веб-сервис.Использование Parallel.ForEach, или реализация ForEachAsync выше отправит их в блоках.

Если вы используете любой из этих методов для ожидания ответов, тогда вам действительно следует ждать GetResponseContentAsync вместо использования .Result:

var responseProduct = await GetResponseContentAsync(restClientProduct, restRequestProduct);

Использование async / await особенно важно в ASP.NET, где существует максимальное количество потоков, которое он может использовать.

...