I'm trying to understand dotnet 7 RateLimiting API, but with no success.
I want to use a PartitionedRateLimiter, so I tried to follow: https://devblogs.microsoft.com/dotnet/announcing-rate-limiting-for-dotnet/. There are difference between the documentation and the article, so I tried to adapt.
First, here is my ClientSideRateLimitedHandler:
internal sealed class ClientSideRateLimitedHandler : DelegatingHandler // , IAsyncDisposable
{
private readonly PartitionedRateLimiter<string>? _rateLimiter;
public ClientSideRateLimitedHandler(PartitionedRateLimiter<string>? rateLimiter) : base(new HttpClientHandler()) =>
_rateLimiter = rateLimiter;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_rateLimiter == null)
{
return await base.SendAsync(request, cancellationToken);
}
using RateLimitLease lease = await
_rateLimiter.AcquireAsync(request.RequestUri.ToString(), permitCount: 1, cancellationToken);
Console.Write($"{DateTime.Now.Second}-{request.RequestUri.ToString()} ");
if (lease.IsAcquired)
{
Console.WriteLine($"* {_rateLimiter.GetStatistics("bookfast")!.CurrentAvailablePermits}");
return await base.SendAsync(request, cancellationToken);
}
Console.WriteLine($"X {_rateLimiter.GetStatistics("bookfast")!.CurrentAvailablePermits}");
var response = new HttpResponseMessage(HttpStatusCode.TooManyRequests);
if (lease.TryGetMetadata(MetadataName.RetryAfter, out TimeSpan retryAfter))
{
response.Headers.Add(
"Retry-After",
((int)retryAfter.TotalSeconds).ToString(
NumberFormatInfo.InvariantInfo));
}
return response;
}
}
And here is the method that creates the PartitionedRateLimiter required by the Handler:
public void SetRateLimits(MarketDataRequest mktRequest, string baseRESTUrl, out PartitionedRateLimiter<string> limiter)
{
limiter = PartitionedRateLimiter.Create<string, string>(resource =>
{
var rateLimiter = RateLimitPartition.GetTokenBucketLimiter("bookfast", key => BookFastOptions);
return rateLimiter;
});
}
I'm aware that the idea of the PartionedRateLimiter would be to select the partition based on the resource (and I did try it), but here is an even simpler code that should (if I understant things correctly) return always the same Bucket and not create a new one on every call.
BucketOptions are:
private TokenBucketRateLimiterOptions BookFastOptions = new TokenBucketRateLimiterOptions()
{
TokenLimit = 7,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 0,
ReplenishmentPeriod = TimeSpan.FromSeconds(3),
TokensPerPeriod = 7,
AutoReplenishment = false
};
When I call my HttpClient, with the ClientHandler, if a look at
_rateLimiter.GetStatistics("bookfast").CurrentAvailablePermits
It is always 6. Somehow It seems that I'm getting a new bucket at every call. My code uses a lot of async/await and the HttpClient is called from different tasks and possibly different threads, but does this make any difference?
I've tried putting the RateLimitPartition in a dictionary to make sure that the method GetTokenBuckerLimiter is called just once, but I get the same result.
What I am doing wrong?
Related
I'm coding my own HttpClient that should Handle HTTP - 429 (TooManyRequests) responses. I'm executing a single method in the client in parallel. As soon as I get a 429 StatusCode as a response, I would like to pause the execution of all Tasks, that are currently calling the method.
Currently, I'm using very old code from an old MS DevBlog: PauseToken/Source
private readonly HttpClient _client;
private readonly PauseTokenSource PauseSource;
private readonly PauseToken PauseToken;
public MyHttpClient(HttpClient client)
{
_client = client;
PauseSource = new();
PauseToken = PauseSource.Token;
}
public async Task<HttpResponseMessage> PostAsJsonAsync<TValue>(string? requestUri?, TValue value, CancellationToken cancellationToken = default)
{
try
{
await PauseToken.WaitWhilePausedAsync(); // I'd really like to pass the cancellationToken as well
HttpResponseMessage result = await _client.PostAsJsonAsync(requestUri, value, cancellationToken).ConfigureAwait(false);
if (result.StatusCodes == HttpStatusCode.TooManyRequests)
{
PauseSource.IsPaused = true;
TimeSpan delay = (result.Headers.RetryAfter?.Date - DateTimeOffset.UtcNow) ?? TimeSpan.Zero;
await Task.Delay(delay, cancellationToken);
PauseSource.IsPaused = false;
return await PostAsJsonAsync(requestUri, value, cancellationToken);
}
return result;
}
finally
{
PauseSource.IsPaused = false;
}
}
MyHttpClient.PostAsJsonAsync is called like this:
private readonly MyHttpClient _client; // This gets injected by the constructor DI
private string ApiUrl; // This as well
public async Task SendToAPIAsync<T>(IEnumerable<T> items, CancellationToken cancellationToken = default)
{
IEnumerable<Task<T>> tasks = items.Select(item =>
_client.PostAsJsonAsync(ApiUrl, item, cancellationToken));
await Task.WhenAll(tasks).ConfigureAwait(false);
}
The items collection will contain 15'000 - 25'000 items. The API is unfortunately built so I have to make 1 request for each item.
I really dislike using old code like this, since I honestly don't even know what it does under the hood (the entire source code can be looked at in the linked article above). Also, I'd like to pass my cancellationToken to the WaitWhilePausedAsync() method since execution should be able to be cancelled at any time.
Is there really no easy way to "pause an async method"?
I've tried to store the DateTimeOffset I get from the result->RetryAfter in a local field, then just simply Task.Delay() the delta to DateTimeOffset.UtcNow, but that didn't seem to work and I also don't think it's very performant.
I like the idea of having a PauseToken but I think there might be better ways to do this nowadays.
I really dislike using old code like this
Just because code is old does not necessarily mean it is bad.
Also, I'd like to pass my cancellationToken to the WaitWhilePausedAsync() method since execution should be able to be cancelled at any time
As far as I can tell, the WaitWhilePausedAsync just returns a task, If you want to abort as soon as the cancellation token is cancelled you could use this answer for an WaitOrCancel extension, used like:
try{
await PauseToken.WaitWhilePausedAsync().WaitOrCancel(cancellationToken );
}
catch(OperationCancelledException()){
// handle cancel
}
Is there really no easy way to "pause an async method"?
To 'pause and async method' should mean we need to await something, since we probably want to avoid blocking. That something need to be a Task, so such a method would probably involve creating a TaskCompletionSource that can be awaited, that completes when unpaused. That seem to be more or less what your PauseToken does.
Note that any type of 'pausing' or 'cancellation' need to be done cooperatively, so any pause feature need to be built, and probably need to be built by you if you are implementing your own client.
But there are might be alternative solutions. Maybe use a SemaphoreSlim for rate-limiting? Maybe just delay the request a bit if you get a ToManyRequests error? Maybe use a central queue of requests that can be throttled?
I ultimately created a library that contains a HttpClientHandler which handles these results for me. For anyone interested, here's the repo: github.com/baltermia/too-many-requests-handler (the NuGet package is linked in the readme).
A comment above led me to the solution below. I used the github.com/StephenCleary/AsyncEx library, that both has PauseTokenSource and the AsyncLock types which provided the functionality I was searching for.
private readonly AsyncLock _asyncLock = new();
private readonly HttpClient _client;
private readonly PauseTokenSource _pauseSource = new();
public PauseToken PauseToken { get; }
public MyHttpClient(HttpClient client)
{
_client = client;
PauseToken = _pauseSource.Token;
}
public async Task<HttpResponseMessage> PostAsJsonAsync<TValue>(string? requestUri?, TValue value, CancellationToken cancellationToken = default)
{
{
// check if requests are paused and wait
await PauseToken.WaitWhilePausedAsync(cancellationToken).ConfigureAwait(false);
HttpResponseMessage result = await _client.PostAsJsonAsync(requestUri, value, cancellationToken).ConfigureAwait(false);
// if result is anything but 429, return (even if it may is an error)
if (result.StatusCode != HttpStatusCode.TooManyRequests)
return result;
// create a locker which will unlock at the end of the stack
using IDisposable locker = await _asyncLock.LockAsync(cancellationToken).ConfigureAwait(false);
// calculate delay
DateTimeOffset? time = result.Headers.RetryAfter?.Date;
TimeSpan delay = time - DateTimeOffset.UtcNow ?? TimeSpan.Zero;
// if delay is 0 or below, return new requests
if (delay <= TimeSpan.Zero)
{
// very important to unlock
locker.Dispose();
// recursively recall itself
return await PostAsJsonAsync(requestUri, value, cancellationToken).ConfigureAwait(false);
}
try
{
// otherwise pause requests
_pauseSource.IsPaused = true;
// then wait the calculated delay
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
}
finally
{
_pauseSource.IsPaused = false;
}
// make sure to unlock again (otherwise the method would lock itself because of recursion)
locker.Dispose();
// recursively recall itself
return await PostAsJsonAsync(requestUri, value, cancellationToken).ConfigureAwait(false);
}
}
I am working on a autocomplete component. When typing, a search function is invoked to return results. This search function e.g. calls a api endpoint. Since loading the results may take some time, I want to be able to cancel previous search requests when there are new requests.
Code:
public Func<string, CancellationToken, Task<IEnumerable<T>>> SearchFuncWithCancel { get; set; }
private CancellationTokenSource _cancellationTokenSrc;
private void CancelToken()
{
_cancellationTokenSrc?.Cancel();
_cancellationTokenSrc = new CancellationTokenSource();
}
private async Task OnSearchAsync()
{
IEnumerable<T> searched_items = Array.Empty<T>();
CancelToken();
try
{
searched_items = (await SearchFuncWithCancel(Text, _cancellationTokenSrc.Token)) ?? Array.Empty<T>();
{
catch (Exception e)
{
Console.WriteLine("The search function failed to return results: " + e.Message);
}
//...
}
For every search any previous (and still active) search is cancelled. I would like to unit test this behaviour.
What I've tried so far:
[Test]
public async Task Autocomplete_Should_Cancel_Search_On_Search()
{
// Arrange
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/autocomplete")
.Respond("application/json", "[\"Foo\", \"Bar\"]");
var client = mockHttp.ToHttpClient();
var comp = Context.RenderComponent<AutocompleteTest1>();
var autocompletecomp = comp.FindComponent<MudAutocomplete<string>>();
autocompletecomp.SetParam(p => p.SearchFuncWithCancel, new Func<string, System.Threading.CancellationToken, Task<IEnumerable<string>>>(async (s, cancellationToken) => {
var result = await client.GetAsync("http://localhost/autocomplete", cancellationToken);
var content = await result.Content.ReadAsStringAsync(cancellationToken);
return JsonConvert.DeserializeObject<IEnumerable<string>>(content);
}));
// Test
autocompletecomp.Find("input").Input("Foo");
comp.WaitForAssertion(() => comp.Find("div.mud-popover").ToMarkup().Should().Contain("Foo"));
autocompletecomp.Find("input").Input("Bar");
comp.WaitForAssertion(() => comp.Find("div.mud-popover").ToMarkup().Should().Contain("Bar"));
}
This test passes, however it does not test the cancellation behaviour yet. I am not sure how to do it. Any ideas?
As long as you can mock the API-call to return something else you should be able to use taskCompletionSources to simulate whatever behavior you desire. I'm not familiar with your specific mock framework, but perhaps something like this should work
// Setup the API method for the first call
CancellationToken cancelToken= null;
var tcs1= new TaskCompletionSource<IEnumerable<T>>();
subjectUnderTest.SearchFuncWithCancel = (t, c) => {
cancelToken= c;
return tcs.Task;
};
var call1 = subjectUnderTest.DoSearch("call1");
Assert.IsFalse(cancelToken.IsCancellationRequested);
// Setup the API-method for the second call
var tcs2= new TaskCompletionSource<IEnumerable<T>>();
subjectUnderTest.SearchFuncWithCancel = (t, c) => tcs2.Task;
var call2 = subjectUnderTest.DoSearch("call2");
Assert.IsTrue(cancelToken.IsCancellationRequested);
// Check that the first call handles a cancelled task correctly
tcs1.SetCanceled();
Assert.IsTrue(call1.IsCanceled);
// Second call succeed
tcs2.SetResult(...);
Assert.IsTrue(call2.IsCompletedSuccessfully);
The first call to the API will return a task that never completes. So when the second call occurs we can check that the cancellation token for the first call is cancelled. I hope the pseudocode is possible to translate to your particular mock framework.
I have several asynec methods.
One of them triggers a POST method which start a process. I then need to 'sample' the results of another GET method every 10 minutes, and check if the status has changed from "pending" to "success" .
I tryed usingSystem.Threading.Timer with no luck, complaining about my method being asynced .
Error CS0407 'Task Campaigns.repeat(object)' has the wrong return type Campaigns
This is my code:
public async Task waitForCampaignLoadAsync(string Uri)
{
...........
var container = JsonConvert.DeserializeObject<CampaignTempleteStatus>(json);
if(container.status == "pending")
{
var autoEvent = new AutoResetEvent(false);
//The next row triggers the error
var stateTimer = new Timer(repeat, autoEvent, 1000, (1000 * 60 * 10));
//How can I keep repeating this, until (bool isFinished = true)??
}
public async Task repeat(Object stateInfo)
{
if(...)
isFinished = true;
}
Another thing is , how do I pass extra info inside repeat function? I need to pass the Uri input for inner ussage ?
When an asynchronous method starts getting complicated it's a sure sign something is wrong. Most of the time async code looks almost the same as synchronous code with the addition of await.
A simple polling loop could be as simple as :
public async Task<string> waitForCampaignLoadAsync(string uri)
{
var client=new HttpClient();
for(int i=0;i<30;i++)
{
token.ThrowIfCancellationRequested();
var json = await client.GetStringAsync(uri);
var container = JsonConvert.DeserializeObject<CampaignTempleteStatus>(json);
if (container.status != "pending")
{
return container.status;
}
await Task.Delay(10000);
}
return "Timed out!";
}
Cancellation in managed threads explains how CancellationTokenSource and CancellationToken can be used to cancel threads, tasks and asynchronous functions. Many asynchronous methods already provide overloads that accept a CancellationToken parameter. The polling function could be modified to accept and check a canellation token :
public async Task<string> waitForCampaignLoadAsync(string uri,CancellationToken token=default)
{
var client=new HttpClient();
for(int i=0;i<30;i++)
{
var json = await client.GetStringAsync(uri);
var container = JsonConvert.DeserializeObject<CampaignTempleteStatus>(json);
if (container.status != "pending")
{
return container.status;
}
await Task.Delay(10000,token);
}
return "Timed out!";
}
A CancellationTokenSource can be used to call this method with an overall timeout of eg, 5 minutes :
var cts=new CancellationTokenSource(TimeSpan.FromMinutes(5));
try
{
var result=waitForCampaignLoadAsync(uri,cts.Token);
//Process the result ....
}
catch(OperationCancelledExcepction ex)
{
//Handle the timeout here
}
This code can be improved. For example, GetStringAsync() doesn't accept a cancellation token. The operation can be broken in two steps though, one call to GetAsync() with a cancellation token that waits for the server to send a result
and another to HttpContent.ReadAsStringAsync() to read the response, eg :
var response=await client.GetAsync(uri,token)
response.EnsureSuccessStatusCode();
var json=await response.Content.ReadAsStringAsync();
...
The first parameter of Timer is a TimerCallback delegate, which should return void
var stateTimer = new Timer(Repeat, autoEvent, 1000, (1000 * 60 * 10));
private void Repeat(object state)
{
....
}
I am using an activity indicator to show to the user while the code goes away and calls to azure.
The call itself works fine and all is working well but the activity indicator loads for a set period of time then afterwards the same delay that I'm trying to prevent to the user still takes place then the next screen loads.
I should probably clarify that I'm relatively new to Xamarin and a lot of this async/await stuff alludes me.
I have put the API calls in a Task and put an await method called sleep in there that runs for about 4 seconds. This was the only way I could get it to run the activity indicator. But it looks like this is also causing the problem, so to summarise I'm stuck.
I want the calls to Azure to take place while the activity indicator is going then when they return open the next page, so as to prevent page lagging and freezing. It does not look good.
So, this is the method that calls to the APIs:
private async Task HandleSubmitCommand()
{
//if (IsLoading) return;
IsLoading = true;
await Sleep();
if (!string.IsNullOrEmpty(IdentityDriver))
{
_entryFieldType = "oid";
_statusResponse = DependencyService.Get<IWebService>().Login(_entryFieldType, IdentityDriver, App.TenantId.Value.ToString());
IsLoading = false;
}
else
{
_entryFieldType = "rid";
_statusResponse = DependencyService.Get<IWebService>().Login(_entryFieldType, IdentityRoute, App.TenantId.Value.ToString());
}
if (_statusResponse == "True")
{
Application.Current.MainPage = new DriverDashboardView();
}
else
Application.Current.MainPage = new NotFoundView();
}
This is the sleep method:
private Task Sleep()
{
return Task.Delay(4000);
}
This is the Login method that calls to the API:
public string Login(string ID, string IDPayload, string TenantID)
{
//await Sleep();
var BaseURL = App.ConFigURL;
var URI = string.Format(BaseURL, TenantID);
using (var httpClient = new HttpClient(new HttpClientHandler()))
{
httpClient.BaseAddress = new Uri(URI);
var Telemetry = new { typeid = ID , id = IDPayload};
var Payload = JsonConvert.SerializeObject(Telemetry);
var SAS = DependencyService.Get<ISasToken>().CreateToken(URI, "RootManageSharedAccessKey", "#####################################");
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", SAS);
var Content = new StringContent(Payload, Encoding.UTF8, "application/json");
var Response = httpClient.PostAsync(URI,Content).Result;
return Response.IsSuccessStatusCode.ToString();
}
}
As I stated the calls to Azure are fine but it doesn't seem to be running asynchronously. It doesn't help that I'm not at all comfortable with async/await.
An ideas?
I want the calls to Azure to take place while the activity indicator is going then when they return open the next page, so as to prevent page lagging and freezing.
The await Sleep(); under your HandleSubmitCommand method is unnecessary. At this point, your login operation would be executed after 4s. Based on your
Login method, you exexute the login operation synchronously via httpClient.PostAsync(URI,Content).Result. You could modify your methods as follows:
public async Task<bool> Login(string ID, string IDPayload, string TenantID)
{
.
.
var Response = await httpClient.PostAsync(URI,Content);
return Response.IsSuccessStatusCode;
}
private async Task HandleSubmitCommand()
{
IsBusy = true;
//call the login operation asynchronously
_statusResponse = await DependencyService.Get<IWebService>().Login(_entryFieldType, IdentityDriver, App.TenantId.Value.ToString());
IsBusy = false;
}
Note: You need to change the definition for your IWebService interface as follows:
public interface IWebService
{
Task<bool> Login(string ID, string IDPayload, string TenantID);
}
I know it has been asked a lot, but my problem is, that my method won't wait for the request to be completet, even though i have implemented a TaskCompletionSource, which should have done the job, but it doesn't.
public DecksViewModel(bool local)
{
DList = new List<Deck>();
if (local)
InitializeLocalDeckList();
else
{
Dereffering();
}
}
public async void Dereffering()
{
var e = await InitilaizeWebDeckList();
List<DeckIn> decksIn = JsonConvert.DeserializeObject<List<DeckIn>>(e);
foreach (DeckIn d in decksIn)
{
Deck dadd = new Deck();
dadd.CardCount = 0;
dadd.Name = d.name;
dadd.PicturePath = d.image;
dadd.InstallDirectory = false;
DList.Add(dadd);
}
DataSource = AlphaKeyGroup<Deck>.CreateGroups(DList, System.Threading.Thread.CurrentThread.CurrentUICulture, (Deck s) => { return s.Name; }, true);
}
public Task<String> InitilaizeWebDeckList()
{
var tcs = new TaskCompletionSource<string>();
var client = new RestClient("blabla.com");
var request = new RestRequest("");
request.AddHeader("Authorization", "Basic blabla");
client.ExecuteAsync(request, response =>
{
test = response.Content;
tcs.SetResult(response.Content);
});
return tcs.Task;
}
So when I call the DecksViewModel constructor, I asyncally try to request the data from a webserver and fill the model.
The point is, that the corresponding view "doesn't wait" for the request to fill the model, so it's displayed empty.
I use the
List<AlphaKeyGroup<Deck>> DataSource
to fill a LongListSelector via DataBinding. But DataSource isn't yet set, when it is binded.
I hope you can help
You're calling an async method without awaiting it inside the constructor. That's why "it doesn't wait" (because it has nothing to wait on).
It's usually a bad idea to call an async method inside the constructor for that reason combined with the fact that constructors can't be async.
You should redesign your solution accordingly. An option is to have an async static method that creates an instance and awaits the procedure:
public static async Task CreateInstance(bool local)
{
var model = new DecksViewModel();
if (local)
{
await InitializeLocalDeckList();
}
else
{
await Dereffering();
}
}
That would allow you to not use async void which should only be used in UI even handlers.
You can read more about other options in Stephen Cleary's blog
You are using async void, which means nobody's gonna wait for that. It's just fire and forget.
I see some misunderstanding in the async keyword here:
Your code will only wait for the result of an async method, if you use await. Otherwise that call will just start the async method, but you don't know when it is actually gonna run.
You cannot use await in constructors though.