我正在查询的API数量有限,您可以每秒查询一次。例如,可能允许您每秒进行20个查询。如果您过度查询服务器,则会收到429错误,阻止您访问API。
问题出现在我的代码中的foreach循环中,其中代码的每次迭代都需要查询API。有没有一种方法可以在限制中进行编码,以便foreach在设置的时间限制内仅查询一定次数,因此我没有达到API的查询限制?或者换句话说,我可以让我的foreach循环以每秒20个循环或每秒任何其他数字的速率进行迭代吗?
如果要查看它,则下面是foreach循环,但我认为您不需要它来回答问题。
foreach(var item in matchlistd)
{
var response2 = client.GetAsync($@"https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/{item.summonerName}apikeyiswhatgoesintherestofthispartoftheapi).Result;
if (response2.IsSuccessStatusCode)
{
var content2 = response2.Content.ReadAsStringAsync().Result;
summonerName player = JsonConvert.DeserializeObject<summonerName>(content2);
accountinfo.Add(player);
}
}
答案 0 :(得分:1)
您应该使用Microsoft的Reactive Framework(又名Rx)-NuGet System.Reactive
并添加using System.Reactive.Linq;
-然后您可以做一些非常酷的事情。
首先,我们需要修复您的代码,以便您不必依赖.Result
调用。
让我们假设您的代码正在以Main
的方法运行-那么您可以更改代码以使其工作如下:
async void Main()
{
// ...
string BuildUrl(string summonerName) => $@"https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/{summonerName}apikeyiswhatgoesintherestofthispartoftheapi";
foreach (var item in matchlistd)
{
var response2 = await client.GetAsync(BuildUrl(item.summonerName));
if (response2.IsSuccessStatusCode)
{
var content2 = await response2.Content.ReadAsStringAsync();
summonerName player = JsonConvert.DeserializeObject<summonerName>(content2);
accountinfo.Add(player);
}
}
// ...
}
请注意async
和两个await
关键字。
现在让我们将循环重写为可观察到的Rx。一个可观察对象就像一个可枚举对象,但是不是立即产生所有值,而是一次产生一个值。
IObservable<summonerName> query =
/* 1 */ from item in matchlistd.ToObservable()
/* 2 */ from response2 in Observable.FromAsync(() => client.GetAsync(BuildUrl(item.summonerName)))
/* 3 */ where response2.IsSuccessStatusCode
/* 4 */ from content2 in Observable.FromAsync(() => response2.Content.ReadAsStringAsync())
/* 5 */ select JsonConvert.DeserializeObject<summonerName>(content2);
matchlistd
枚举变成可观察的client.GetAsync
并使用Observable.FromAsync
解包任务以创建可观察到的消息响应response2.IsSuccessStatusCode == false
response2.Content.ReadAsStringAsync()
并使用Observable.FromAsync
解包任务以创建可观察的string
string
转换为summonerName
。然后您可以执行以下操作以获取所有结果并将其放入列表中:
accountinfo.AddRange(await query.ToList());
现在,我们只想使其每秒最多产生20个查询。这是修改后的查询:
IObservable<summonerName> query =
from items in matchlistd.ToObservable().Buffer(20).Zip(Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0)), (x, t) => x)
from item in items
from response2 in Observable.FromAsync(() => client.GetAsync(BuildUrl(item.summonerName)))
where response2.IsSuccessStatusCode
from content2 in Observable.FromAsync(() => response2.Content.ReadAsStringAsync())
select JsonConvert.DeserializeObject<summonerName>(content2);
请注意以下部分:
from items in matchlistd.ToObservable().Buffer(20).Zip(Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0)), (x, t) => x)
from item in items
那是秘诀。 .Buffer(20)
和TimeSpan.FromSeconds(1.0)
是可以更改以自定义行为的位。
答案 1 :(得分:0)
使用如下计时器。您还可以为计时器间隔传递一个变量。
private Timer _functionTimer;
public void InitMatchList()
{
_functionTimer_Tick = new Timer();
_functionTimer_Tick.Tick += new EventHandler(_functionTimer_Tick);
_functionTimer_Tick.Interval = 50; // in miliseconds
_functionTimer_Tick.Start();
}
private void _functionTimer_Tick(object sender, EventArgs e)
{
var response2 = client.GetAsync($@"https://na1.api.riotgames.com/lol/summoner/v4/summoners/by-name/{item.summonerName}apikeyiswhatgoesintherestofthispartoftheapi).Result;
if (response2.IsSuccessStatusCode)
{
var content2 = response2.Content.ReadAsStringAsync().Result;
summonerName player = JsonConvert.DeserializeObject<summonerName>(content2);
accountinfo.Add(player);
}
}
答案 2 :(得分:0)
虽然计时器可以工作,但这是限制速率的传统方法。如果服务器每秒不希望超过20个并发连接,则可能无法正常工作。这意味着,如果结果花费的时间比下一个 timed-request 时间长,您可能还会得到429。替代解决方案可能看起来像:
private static readonly httpClient = new HttpClient();
public async Task<IEnumerable<string>> GetAPIResults(IEnuemrable<MatchList> matchLists,
int maximumRequestsPerSecond)
{
var requests = matchLists
.Select(ml => new RequestStatus { MatchList = ml })
.ToList();
foreach (var request in requests)
{
var activeRequests = RequestStatus
.Where(rs =>
(rs.RequestedOn.HasValue && rs.RequestedOn > DateTime.Now.AddSeconds(-1))
|| (rs.Task.HasValue && rs.Task.TaskStatus != TaskStatus.Running))
.ToList();
//wait for either a request to complete
//or for a request not active within the last second to expire
while (activeRequests > maximumRequestsPerSecond)
{
var lastActive = activeRequests.OrderBy(RequestedOn.Value).First();
var waitFor = DateTime.Now - lastActive.RequestedOn.Value;
// or maybe this to be safe
// var waitFor = (DateTime.Now - lastActive.RequestedOn.Value)
// .Add(TimeSpan.FromMilliseconds(100));
await Task.Delay(waitFor);
activeRequests = RequestStatus
.Where(rs =>
(rs.RequestedOn.HasValue && rs.RequestedOn > DateTime.Now.AddSeconds(-1))
|| (rs.Task.HasValue && rs.Task.TaskStatus != TaskStatus.Running))
.ToList();
}
request.RequestTask = httpClient.GetStringAsync(myUrl);
}
await Task.WhenAll(requests.Select(r => r.RequestTask.Value));
// not sure about .Result here...
return requests.Select(r => r.RequestTask.Value.Result).ToList();
// probably safer:
return requests.Select(r => await r.RequestTask).ToList();
}
public class RequestStatus
{
public MatchList MatchList { get; set; }
public DateTime? RequestedOn { get; set }
public Task<string>? RequestTask { get; set; }
}
如果有一个Task.WhenAll()
任务弹出了CancellationToken
的{{1}}方法,而不是仅仅等待特定的时间,则延迟可能会更好。