c# - 使用循环限制并发异步请求

标签 c# asp.net .net async-await semaphore

我目前正致力于向 Web API 发出大量请求。我已尝试异步此过程,以便我可以在合理的时间内这样做,但是我无法限制连接,因此我不会发送超过 10请求/秒。我正在使用信号量进行节流,但我不完全确定它在这种情况下如何工作,因为我有一个嵌套循环。

我实际上是在获取模型列表,每个模型中都有一个天数列表。我需要为模型中的每一天提出请求。天数可以在 1 到大约 50 之间的任何地方,99% 的时间只会是 1。所以我想 async 每个模型,因为它们大约有 3000,但我想 async 在有的情况下的日子需要完成的多天。我需要保持在 10 请求/秒或以下,所以我认为最好的方法是将整个操作的请求限制设置为 10。有什么地方可以放置信号量来限制整个链的连接?

每个单独的请求还必须对 2 不同的数据发出两次请求,并且此 API 目前不支持任何类型的批处理。

我是 c# 的新手,async 的新手,WebRequests/HttpClient 的新手,因此非常感谢您的帮助。我试图在这里添加所有相关代码。如果您需要任何其他信息,请告诉我。

public static async Task GetWeatherDataAsync(List<Model> models)
{
    SemaphoreSlim semaphore = new SemaphoreSlim(10);
    var taskList = new List<Task<ComparisonModel>>();

    foreach (var x in models)
    {
        await semaphore.WaitAsync();
        taskList.Add(CompDaysAsync(x));
    }

    try
    {
        await Task.WhenAll(taskList.ToArray());
    }
    catch (Exception e) { }
    finally
    {
        semaphore.Release();
    }
}

public static async Task<Models> CompDaysAsync(Model model)
{
    var httpClient = new HttpClient();
    httpClient.DefaultRequestHeaders.Authorization = new 
                Headers.AuthenticationHeaderValue("Token","xxxxxxxx");
    httpClient.Timeout = TimeSpan.FromMinutes(5);
    var taskList = new List<Task<Models.DateTemp>>();

    foreach (var item in model.list)
    {
        taskList.Add(WeatherAPI.GetResponseForDayAsync(item, 
            httpClient, Latitude, Longitude));
    }
    httpClient.Dispose();
    try
    {
        await Task.WhenAll(taskList.ToArray());
    }
    catch (Exception e) { }

    return model;
}

public static async Task<DateTemp> GetResponseForDayAsync(DateTemp date, HttpClient httpClient, decimal? Latitude, decimal? Longitude)
{
    var response = await httpClient.GetStreamAsync(request1);
    StreamReader myStreamReader = new StreamReader(response);
    string responseData = myStreamReader.ReadToEnd();
    double[] data = new double[2];
    if (responseData != "[[null, null]]")
    {
        data = Array.ConvertAll(responseData.Replace("[", "").Replace("]", "").Split(','), double.Parse);
    }
    else { data = null; };

    double precipData = 0;
    var response2 = await httpClient.GetStreamAsync(request2);
    StreamReader myStreamReader2 = new StreamReader(response2);
    string responseData2 = myStreamReader2.ReadToEnd();
    if (responseData2 != null && responseData2 != "[null]" && responseData2 != "[0.0]")
    {
        precipData = double.Parse(responseData2.Replace("[", "").Replace("]", ""));
    }
    date.Precip = precipData;

    if (data != null)
    {
        date.minTemp = data[0];
        date.maxTemp = data[1];
    }
    return date;
}

最佳答案

我认为您完全不了解SemaphoreSlim 的作用。

  1. 您的信号量是一个基于方法级的局部变量,因此每个 GetWeatherDataAsync 方法调用都会产生对您的 API 的 10 次调用,而无需等待对于其他客户。
  2. 此外,如果 models.Count > 10,您的代码将死锁,因为您在每次迭代中等待信号量,这些请求被堆叠起来,并且对于 11th code> 你的线程将永远挂起,因为你没有释放信号量:

    var semaphore = new SemaphoreSlim(10);
    
    foreach (var item in Enumerable.Range(0, 15))
    {
        // will stop after 9
        await semaphore.WaitAsync();
        Console.WriteLine(item);
    }
    

你真正需要做的是将信号量移动到实例级别(或者甚至使用 static 关键字的类型级别),并在内部等待它 GetWeatherDataAsync,并将 Release 放在 finally block 中。

至于 Parallel.Foreach - 你不应该在这种情况下使用它,因为它不知道 async 方法(它是在 async/await 之前引入的) ),并且您的方法看起来不像是 CPU 密集型的。

关于c# - 使用循环限制并发异步请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44527171/

相关文章:

c# - 如何在 listView OnItemEditing 中获取 DataItem?

javascript - 如何阻止 asp listview 中的动态侧边栏崩溃?

.net - BindingRedirect 到不同的程序集名称

.net - PowerShell调用命令行

c# - 使用 C# 创建 Excel 加载项

c# - 通过 .NET 中的某种接口(interface)使属性可用于数据绑定(bind)?

c# - 在 ASP.NET 中动态填充时间下拉菜单

c# - 将 JSON 反序列化为对象时出现问题

C# 获取 Windows 服务启动的日期/时间

c# - 线程化简单计算