I have a decorator class that adds the ability to rate limit http requests for hitting an API:
public class RateLimitedHttpClient : IHttpClient
{
public RateLimitedHttpClient(System.Net.Http.HttpClient client)
{
_client = client;
_client.Timeout = TimeSpan.FromMinutes(30);
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
}
public async Task<string> ReadAsync(string url)
{
if (!_sw.IsRunning)
_sw.Start();
await Delay();
using var response = await _client.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
private async Task Delay()
{
var totalElapsed = GetTimeElapsedSinceLastRequest();
while (totalElapsed < MinTimeBetweenRequests)
{
await Task.Delay(MinTimeBetweenRequests - totalElapsed);
totalElapsed = GetTimeElapsedSinceLastRequest();
};
_timeElapsedOfLastHttpRequest = (int)_sw.Elapsed.TotalMilliseconds;
}
private int GetTimeElapsedSinceLastRequest()
{
return (int)_sw.Elapsed.TotalMilliseconds - _timeElapsedOfLastHttpRequest;
}
private readonly System.Net.Http.HttpClient _client;
private readonly Stopwatch _sw = new Stopwatch();
private int _timeElapsedOfLastHttpRequest;
private const int MinTimeBetweenRequests = 100;
}
However, I'm noticing that at the line indicated below I get a message in the debugger that says that the next statement will execute when the current thread returns.
var epsDataPoints = await _downloader.GetEPSData(cik);
foreach (var eps in epsDataPoints)
{
// getting VS2019 debugger message here!
// assuming that the line above is deadlocking....
Console.WriteLine($"{cik} :: {eps.DateInterval} :: {eps.EPS}");
}
When I open up the Task Manager, the network bandwidth goes to 0 and everything stops with the application other than it sitting at the Console.WriteLine above.
The EPSDownloader class that uses the IHttpClient is below:
public class EPSDownloader
{
public EPSDownloader(IHttpClient client)
{
_client = client;
}
public async Task<IEnumerable<EPSDataPoint>> GetEPSData(int cik)
{
var epsDataPoints = new Dictionary<LocalDate, EPSDataPoint>();
var reportLinks = await GetReportLinks(cik);
foreach (var reportLink in reportLinks)
{
var xbrlLink = await GetXBRLLink(reportLink);
var epsData = await GetEPSData(xbrlLink);
foreach (var eps in epsData)
{
if (!epsDataPoints.ContainsKey(eps.DateInterval.End))
epsDataPoints.Add(eps.DateInterval.End, eps);
}
}
var list = epsDataPoints.OrderBy(d => d.Key).Select(e => e.Value).ToList();
return list;
}
private async Task<IList<string>> GetReportLinks(int cik)
{
// move this url elsewhere
var url = "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&CIK=" + cik +
"&type=10&dateb=&owner=include&count=100";
var srBody = await _client.ReadAsync(url); // consider moving this to srPage
var srPage = new SearchResultsPage(srBody);
return srPage.GetAllReportLinks();
}
private async Task<string> GetXBRLLink(string link)
{
var url = SEC_HOSTNAME + link;
var fdBody = await _client.ReadAsync(url);
var fdPage = new FilingDetailsPage(fdBody);
return fdPage.GetInstanceDocumentLink();
}
private async Task<IList<EPSDataPoint>> GetEPSData(string xbrlLink)
{
var xbrlBody = await _client.ReadAsync(SEC_HOSTNAME + xbrlLink);
var xbrlDoc = new XBRLDocument(xbrlBody);
return xbrlDoc.GetAllQuarterlyEPSData();
}
private readonly IHttpClient _client;
private const string SEC_HOSTNAME = "https://www.sec.gov";
}
It seems to be that there is an issue with HttpClient, but I don't know why. No exceptions are being thrown, but I do occasionally see that threads have exited with code 0.
Update: I actually restarted my computer while the application was running and it began running fine again for about 20 minutes before the Task Manager showed 0 for the network speed and the application just sat there.
Related
I am bit frustrated now what's wrong with my code, and I hope you guys can help me with it, so here are the things I have tried.
so I tried making the HttpClient static, and I tried using the IHttpClientFactory.CreateClient() and I even added this on my .csproj
<ServerGarbageCollection>false</ServerGarbageCollection>
Here is the sample code that I have been doing
public class TestController : BaseController
{
private static HttpClient _httpClient = new();
public TestController()
{
}
[HttpGet("bills")]
public async Task<IActionResult> GetBillsPresentment([FromQuery] GetBillPresentmentQuery query)
{
if (!query.AccountNumber.Contains("-"))
query.AccountNumber = FormatAccountNumber(query.AccountNumber);
var billDetails = await GetBillDetail(query.AccountNumber);
if (billDetails == null)
throw new ProviderProcessException(ProviderErrorCode.INVALID_ACCOUNT_NUMBER);
return Ok(new BillPresentmentVm
{
User = new CustomerDto
{
CustomerName = billDetails.Name
},
Billing = new BillingDto
{
AccountNumber = query.AccountNumber,
DueDate = DateTime.Parse(billDetails.LastReadDate).AddMonths(1),
Outstanding = !string.IsNullOrEmpty(billDetails.Arrears) ? decimal.Parse(billDetails.Arrears) : null
}
});
}
private async Task<ResponseModel> GetBillDetail(string accountNumber)
{
try
{
var payload = new { accno = accountNumber };
string json = JsonConvert.SerializeObject(payload);
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
using var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.PostAsync("https://test.com", byteContent);
if (!response.IsSuccessStatusCode)
throw new ProviderProcessException(ProviderErrorCode.BILLING_CYCLE_UNAVAILABLE);
var result = await response.Content.ReadAsStringAsync();
if (result == "Accno not found!") return null;
var data = JsonConvert.DeserializeObject<ResponseModel>(result);
return data;
}
catch (Exception)
{
throw new ProviderProcessException(ProviderErrorCode.BILLING_CYCLE_UNAVAILABLE);
}
}
private static string FormatAccountNumber(string accountNumber)
{
return string.Format("{0:#######-########}", Convert.ToInt64(accountNumber));
}
}
And here's the docker memory usage
The memory usage keeps increasing after a request. Can someone explains me why it is not decreasing?
Thank you very much in advance
I solve this issue using IHttpClientFactory instead of HttpClient.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
I use some thing like this and it works fine on large amount of requests per second and it use memory in normal way .
namespace BehsaLoyalty.ApiClient
{
public class ApiRepo : IApiRepo
{
private readonly IHttpClientFactory _HttpClientFactory;
public ApiRepo (IHttpClientFactory httpClientFactory)
{
_HttpClientFactory = httpClientFactory;
}
public async Task<ResponseModel> PostMyObject(Myobject model, CancellationToken cancellationToken)
{
HttpClient httpClient = _HttpClientFactory.CreateClient("ApiDestinationURI");
using HttpRequestMessage request = new(HttpMethod.Post, "/blah/blah");
request.Content = new StringContent(JsonSerializer.Serialize(model));
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken);
response.EnsureSuccessStatusCode();
string createdContent = await response.Content.ReadAsStringAsync();
ResponseModel ResponseReturn = JsonSerializer.Deserialize<ResponseModel>(createdContent);
return ResponseReturn;
}
}
}
I want to connect to my Web api with these code
but when debugger arrived on
var response = await client.GetAsync(url);
skip without any error,
UPDATE
it's mean:
I set breakpoint and when debugger arrive on that line after that don't catch the next line and exit from this block and jump to caller method
public class Constants
{
public const string BaseUrl = "https://localhost:44301/";
}
2:
public static class GeneralUrl
{
public static string ComapnyNameUrl()
{
return string.Format(Constants.BaseUrl);
}
}
3: change to 3.1
public async static Task<List<CompanyName>> GetCompanyNamesAsync()
{
List<CompanyName> companyNameList = new List<CompanyName>();
string url = GeneralUrl.ComapnyNameUrl() + "api/CompanyName";
using (HttpClient client = new HttpClient())
{
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
string content =await response.Content.ReadAsStringAsync();
companyNameList = JsonConvert.DeserializeObject<List<CompanyName>>(content);
}
}
return companyNameList;
}
UPDATE 2
3.1:
I change above code to this but the problen exist
Update
public class CompanyNameService
{
private static HttpClient client = new HttpClient();
public async static Task<List<CompanyName>> GetCompanyNamesAsync()
{
List<CompanyName> companyNameList = new List<CompanyName>();
string url = GeneralUrl.ComapnyNameUrl() + "api/Companyname";
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
string content =await response.Content.ReadAsStringAsync();
companyNameList = JsonConvert.DeserializeObject<List<CompanyName>>(content);
}
return companyNameList;
}
}
also my startup.cs in my web api is
services.AddControllers().AddJsonOptions(o =>
{
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.JsonSerializerOptions.MaxDepth = 0;
});
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore);
END OF UPDATE 2
and final for calling them is=>
4:
protected override async void OnAppearing()
{
base.OnAppearing();
var companyName = await CompanyNameService.GetCompanyNamesAsync();
}
I think that it was for async method what is my mistake??
UPDATE
In debug mode in output windows I have this message
02-10 13:04:21.568 I/Choreographer( 790): Skipped 428 frames! The application may be doing too much work on its main thread.
02-10 13:04:21.642 I/OpenGLRenderer( 790): Davey! duration=7213ms; Flags=0, IntendedVsync=51372290598463, Vsync=51379423931511, OldestInputEvent=9223372036854775807, NewestInputEvent=0, HandleInputStart=51379430268200, AnimationStart=51379430347200, PerformTraversalsStart=51379430673700, DrawStart=51379441519600, SyncQueued=51379446948700, SyncStart=51379447122900, IssueDrawCommandsStart=51379447270300, SwapBuffers=51379483411800, FrameCompleted=51379504170100, DequeueBufferDuration=409000, QueueBufferDuration=612000,
UPDATE 3:
As I try use local address, after more than 5 min got a message in out put window that "the operation in canceled" and catch the exception with the same message.
Try this:
protected async override void OnAppearing()
{
base.OnAppearing();
var companyName = await GetCompanyNamesAsync();
}
public async static Task<List<CompanyName>> GetCompanyNamesAsync()
{
List<CompanyName> companyNameList = new List<CompanyName>();
string url = GeneralUrl.ComapnyNameUrl() + "api/Companyname";
using (HttpClient client = new HttpClient())
{
var response = await client.GetStringAsync(url);
companyNameList = JsonConvert.DeserializeObject<List<CompanyName>>(response);
}
return companyNameList;
}
I'm using bot framework V3 with C#.
I need to identify when my bot is idle for more than 5 minutes.
I've tried to handle bot idleness via the MessageController but my attempt seems not to work out.
switch (activity.Type)
{
case ActivityTypes.Message:
await Task.Delay(5000).ContinueWith(async (t) =>
{
var reply = activity.CreateReply();
var myMessage = "Bot time out. Bye";
reply.Text = myMessage;
await connector.Conversations.ReplyToActivityAsync(reply);
});
await Task.Factory.StartNew(() => Conversation.SendAsync(activity, () => new Dialogs.RootDialog(luisService).DefaultIfException()));
}
break;
}
What could be wrong?
Any sample could you could share please?
Thx in advance!
First, you're only delaying for 5 seconds (5000 miliseconds) and not 5 minutes.
Anyhow, you can try the following approach. Add this class:
public static class TimeoutConversations
{
const int TimeoutLength = 10;
private static Timer _timer;
private static TimeSpan _timeoutLength;
static TimeoutConversations()
{
_timeoutLength = TimeSpan.FromSeconds(TimeoutLength);
_timer = new Timer(CheckConversations, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
}
static ConcurrentDictionary<string, UserInfo> Conversations = new ConcurrentDictionary<string, UserInfo>();
static async void CheckConversations(object state)
{
foreach (var userInfo in Conversations.Values)
{
if (DateTime.UtcNow - userInfo.LastMessageReceived >= _timeoutLength)
{
UserInfo removeUserInfo = null;
Conversations.TryRemove(userInfo.ConversationReference.User.Id, out removeUserInfo);
var activity = userInfo.ConversationReference.GetPostToBotMessage();
//clear the dialog stack and conversation state for this user
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, activity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
//botData.UserData.Clear();
botData.ConversationData.Clear();
botData.PrivateConversationData.Clear();
await botData.FlushAsync(CancellationToken.None);
}
MicrosoftAppCredentials.TrustServiceUrl(activity.ServiceUrl);
var connectorClient = new ConnectorClient(new Uri(activity.ServiceUrl), ConfigurationManager.AppSettings["MicrosoftAppId"], ConfigurationManager.AppSettings["MicrosoftAppPassword"]);
var reply = activity.CreateReply("I haven't heard from you in awhile. Let me know when you want to talk.");
connectorClient.Conversations.SendToConversation(reply);
//await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
}
}
}
public static void MessageReceived(Activity activity)
{
UserInfo userInfo = null;
if (Conversations.TryGetValue(activity.From.Id, out userInfo))
{
userInfo.LastMessageReceived = DateTime.UtcNow;
}
else
{
Conversations.TryAdd(activity.From.Id, new UserInfo()
{
ConversationReference = activity.ToConversationReference(),
LastMessageReceived = DateTime.UtcNow
});
}
}
}
public class UserInfo
{
public ConversationReference ConversationReference { get; set; }
public DateTime LastMessageReceived { get; set; }
}
And then in messages controller, call:
TimeoutConversations.MessageReceived(activity);
In this example, it is doing a 10 second timeout, checking every 5 seconds. This is a basic (somewhat sloppy) timer to time out conversations. You'll probably run into bugs, but you can tweak it until it fits your needs. Using an azure queue or something might be better.
Here is a DCR for v4 to implement this basic functionality:
https://github.com/microsoft/botframework-sdk/issues/5237
public class RollingRequests
{
private const int DefaultNumSimultaneousRequests = 10;
private readonly HttpClient _client; // Don't worry about disposing see https://stackoverflow.com/questions/15705092/do-httpclient-and-httpclienthandler-have-to-be-disposed
private readonly HttpCompletionOption _httpCompletionOption;
private readonly int _numSimultaneousRequests;
public RollingRequests() : this(DefaultNumSimultaneousRequests)
{
}
public RollingRequests(int windowSize) : this(new HttpClient(), windowSize)
{
}
public RollingRequests(HttpClient client, int numSimultaneousRequests, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
{
_client = client;
_numSimultaneousRequests = numSimultaneousRequests;
_httpCompletionOption = httpCompletionOption;
}
public async Task ExecuteAsync(List<string> urls, CancellationToken cancellationToken, Action<HttpResponseHeaders, string> requestCallback = null)
{
var nextIndex = 0;
var activeTasks = new List<Task<Tuple<string, HttpResponseMessage>>>();
var startingIndex = Math.Min(_numSimultaneousRequests, urls.Count);
for (nextIndex = 0; nextIndex < startingIndex; nextIndex++)
{
activeTasks.Add(RequestUrlAsync(urls[nextIndex], cancellationToken));
}
while (activeTasks.Count > 0)
{
var finishedTask = await Task.WhenAny(activeTasks).ConfigureAwait(false);
activeTasks.Remove(finishedTask);
var retryUrl = await ProcessTask(await finishedTask, requestCallback).ConfigureAwait(false);
// If retrying, add the URL to the end of the queue
if (retryUrl != null)
{
urls.Add(retryUrl);
}
if (nextIndex < urls.Count)
{
activeTasks.Add(RequestUrlAsync(urls[nextIndex], cancellationToken));
nextIndex++;
}
}
}
private async Task<string> ProcessTask(Tuple<string, HttpResponseMessage> result, Action<HttpResponseHeaders, string> requestCallback = null)
{
var url = result.Item1;
using (var response = result.Item2)
{
if (!response.IsSuccessStatusCode)
{
return url;
}
if (requestCallback != null)
{
string content = null;
if (_httpCompletionOption == HttpCompletionOption.ResponseContentRead)
{
content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
requestCallback(response.Headers, content);
}
return null;
}
}
private async Task<Tuple<string, HttpResponseMessage>> RequestUrlAsync(string url, CancellationToken ct)
{
var response = await _client.GetAsync(url, _httpCompletionOption, ct).ConfigureAwait(false);
return new Tuple<string, HttpResponseMessage>(url, response);
}
}
This is a class that allows for X simultaneous requests to be on-going at once. When I am unit-testing this class and I moq the HttpClient giving each request a 1 second delay the initial activeTasks.Add is taking 5 seconds if I have 5 requests, suggesting to me that RequestUrlAsync isn't truly async.
Can anyone spot the issue?
Edit:
This is how I am sleeping the mocked client
_messageHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(MethodToMoq, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Callback(() => Thread.Sleep(1000))
.ReturnsAsync(callback)
.Verifiable();
I tested your class RollingRequests with actual urls and works as expected. Then I replaced await _client.GetAsync(... with await Task.Delay(1000) and continued working as expected. Then replaced the same line with Thread.Sleep(1000) and replicated your problem.
Moral lesson: avoid blocking the current thread when running asynchronous code!
(It would be easier to answer if you had provided a Minimal, Reproducible Example)
Mixing Thread.Sleep with asynchronous code isn't a good idea because it is a blocking call.
Mocking internals should also be avoided.
Here's a simple example of a test that takes about 1 second to execute 10 requests:
async Task Test()
{
var httpClient = new HttpClient(new TestHttpMessageHandler());
var ticks = Environment.TickCount;
await Task.WhenAll(Enumerable.Range(0, 10).Select(_ => httpClient.GetAsync("https://stackoverflow.com/")));
Console.WriteLine($"{Environment.TickCount - ticks}ms");
}
class TestHttpMessageHandler : HttpMessageHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await Task.Delay(1000);
return new HttpResponseMessage();
}
}
I have the code below in a console app. The LookUpUser method gets called and PostAsJsonAsync gets called but breakpoints in the response checking don't get hit afterwards. What am I doing incorrectly in this implementation?
static void Main(string[] args)
{
TestUseCase().GetAwaiter().GetResult();
}
private static async Task TestUseCase()
{
await GetUserGuids();
}
private static async Task GetUserGuids()
{
var userGuids = new List<Guid>();
userGuids.Add(Guid.Parse("7b5cf09c-196c-4e0b-a0e2-0683e4f11213"));
userGuids.Add(Guid.Parse("3a636154-b7fc-4d96-9cd1-d806119ff79f"));
userGuids.ForEach(async x => await LookUpUser(x));
}
private static async Task LookUpUser(Guid adUserGuid)
{
var client = new HttpClientManager().GetHttpClient();
var response = await client.PostAsJsonAsync("api/v1/users/search", new { ADUserGuid = adUserGuid });
if (response.IsSuccessStatusCode)
{
var groups = await response.Content.ReadAsAsync<List<User>>();
}
else //not 200
{
var message = await response.Content.ReadAsStringAsync();
}
}
userGuids.ForEach(async x => await LookUpUser(x));
The delegate in the ForEach is basically a async void (fire and forget)
Consider selecting a collection of Task and then use Task.WhenAll
private static async Task GetUserGuids() {
var userGuids = new List<Guid>();
userGuids.Add(Guid.Parse("7b5cf09c-196c-4e0b-a0e2-0683e4f11213"));
userGuids.Add(Guid.Parse("3a636154-b7fc-4d96-9cd1-d806119ff79f"));
var tasks = userGuids.Select(x => LookUpUser(x)).ToArray();
await Task.WhenAll(tasks);
}
Also assuming HttpClientManager.GetHttpClient() returns a HttpClient there is no need to create multiple instances. on static client should do
static HttpClient client = new HttpClientManager().GetHttpClient();
private static async Task LookUpUser(Guid adUserGuid) {
var response = await client.PostAsJsonAsync("api/v1/users/search", new { ADUserGuid = adUserGuid });
if (response.IsSuccessStatusCode) {
var groups = await response.Content.ReadAsAsync<List<User>>();
} else {
//not 200
var message = await response.Content.ReadAsStringAsync();
}
}
I got it to work by changing the ForEach to:
foreach (var guid in userGuids)
{
await LookUpUserInSecurityApi(guid);
}