I've been working for a few days on a performance problem.
Before I delve deeper I want to quickly explain how the specific service work.
I have a main Service that get a request and send requests to other micro-services but the single Entry Point for the user is to Main service, I thinks is more simple to understand with this image:
After the Main service get request from API he do some logic, query the Db and then get a list, every item on the list has Id, to get enrichment about every item the main service create request to one of the micro-service.
For example John request main service, main service get from Db a list of 90 items then the main service will create 90 calls to micro service and return to John single response that include 90 items.
Now the question is only about the right way to create async call to micro service.
This how I develop this part:
GetDetailsAsync(Id, result.Items, request.SystemComponentId);
private static void GetDetailsAsync(string Id, List<MainItem> items, int systemId)
{
var getDetailsTasks = new List<Task>();
foreach (MainItem single in items)
{
getDetailsTasks.Add(SetSingleDetailsAsync(Id, single, systemId));
}
Task.WhenAll(getDetailsTasks);
}
private static async Task SetSingleDetailsAsync(string Id, MainItem single, int systemId)
{
single.ActivityExtendedDetails = await ProcessItemDetailsRequest.GetItemDetailsAsync(Id, single.TypeId,
single.ItemId, systemId);
}
public static Task<JObject> GetItemDetailsAsync(string id, short type,
string itemId, int systemId)
{
var typeList = ActivityTypeDetails.GetActivityTypes();
var url = GetActivityUrl(id, type, itemId, typeList);
if (url == null)
{
throw new Failure($"No url defined for type {type}");
}
try
{
JObject res;
using (var stream = client.GetStreamAsync(url).Result)
using (var sr = new StreamReader(stream))
using (var reader = new JsonTextReader(sr))
{
var serializer = new JsonSerializer();
res = serializer.Deserialize<JObject>(reader);
}
return Task.FromResult(res);
}
catch(Exception ex)
{
Logger.Warn(
$"The uri {url} threw exception {ex.Message}.");
//[Todo]throw exception
return null;
}
}
This code run and the result is not good enough, the CPU rises very quickly and becomes very high, I think that I has a problem on GetItemDetailsAsync func because I use client.GetStreamAsync(url).Result
when using .Result it's block until the task is completed.
So I do some minor change on GetItemDetailsAsync to try to be really async:
public static async Task<JObject> GetItemDetailsAsync(string id, short type,
string itemId, int systemId)
{
var typeList = ActivityTypeDetails.GetActivityTypes();
var url = GetActivityUrl(id, type, itemId, typeList);
if (url == null)
{
throw new Failure($"No url defined for type {type}");
}
try
{
JObject res;
using (var stream = await client.GetStreamAsync(url))
using (var sr = new StreamReader(stream))
using (var reader = new JsonTextReader(sr))
{
var serializer = new JsonSerializer();
res = serializer.Deserialize<JObject>(reader);
}
return res;
}
catch(Exception ex)
{
Logger.Warn(
$"The uri {url} threw exception {ex.Message}.");
//[Todo]throw exception
return null;
}
}
But now I get null where I supposed to get the data that come from Async function.
I try to debugging and I noticed something weird, everything happen likes as I would expect: the methods was called, request to micro-service was executed and get response but the response from the End-Point(which is found on main-service) return before the async method return from micro-service, that cause that I get null instead of my expected data.
I thinks that maybe I don't use correctly async\await and would be happy if anyone could explain how this behavior happens
Related
I'm trying to list all items under a path for a determined bucket using Google Cloud Storage APIs. Under this path, there are more than 1000 items - which is the maximum number of items that ListObjects / ListObjectsAsync return.
In order to be able to repeat the call and get the next 1000 items, I need the NextPageToken from the previous response, that's how result pagination gets done! It's easy and reasonable.
However, I never get a NextPageToken within the responses. Am I missing something? This is my code so far. Some key points:
Althought there are more than +1000 items sharing the same prefix, I only get one response while looping through them in the enumerator.
The NextPageToken in the Console.WriteLine statement is always null.
Repeating the same call returns the same first 1000 objects over and over.
async IAsyncEnumerable<string> ListObjectsAsync(
string prefix, [EnumeratorCancellation] CancellationToken cancelToken)
{
var listObjectsOptions = new ListObjectsOptions
{
Fields = "items(name)"
};
var rawResponses = mGoogleClient.ListObjectsAsync(
mBucketName,
prefix,
listObjectsOptions).AsRawResponses();
using (var enumerator = rawResponses.GetEnumerator())
{
while (await enumerator.MoveNext(cancelToken))
{
foreach (var googleObject in enumerator.Current.Items)
yield return googleObject.Name;
}
Console.WriteLine(enumerator.Current.NextPageToken);
}
}
Thank you.
Apparently, I'm missing the nextPageToken field in the ListObjectsOptions used to make the request. Without specifying that field, the service won't return it - because of who knows why!
This code should work:
async IAsyncEnumerable<string> ListObjectsAsync(
string prefix, [EnumeratorCancellation] CancellationToken cancelToken)
{
var listObjectsOptions = new ListObjectsOptions
{
Fields = "items(name),nextPageToken"
};
var rawResponses = mGoogleClient.ListObjectsAsync(
mBucketName,
prefix,
listObjectsOptions).AsRawResponses();
using (var enumerator = rawResponses.GetEnumerator())
{
while (await enumerator.MoveNext(cancelToken))
{
foreach (var googleObject in enumerator.Current.Items)
yield return googleObject.Name;
}
Console.WriteLine(enumerator.Current.NextPageToken);
}
}
The nice thing is that you don't even have to use the NextPageToken explicitly. This is, you don't have to do something like this:
string token = null;
do
{
var options = new ListObjectsOptions
{
Fields = "items(name),nextPageToken",
PageToen = token
};
var rawResponses = mGoogleClient.ListObjectsAsync(
mBucketName,
prefix,
listObjectsOptions).AsRawResponses();
using (var enumerator = rawResponses.GetEnumerator())
{
while (await enumerator.MoveNext(cancelToken))
{
foreach (var googleObject in enumerator.Current.Items)
yield return googleObject.Name;
}
token = enumerator.Current.NextPageToken;
}
}
while (!string.IsNullOrEmpty(token));
...because the enumerator you got in rawResponses.GetEnumerator() will take care of using the token of a response to automatically fetch the next (if needed) while iterating them.
So the first piece of code is valid to iterate over +1000 objects in a single call.
I seem to be facing some issue when trying to read the JSON response from Bungie API, the below method usually works but for some reason i am now getting error message.
Function Code
public async Task<List<string>> GetMemberID(string MembersName)
{
List<string> MembershipID = new List<string>();
HttpResponseMessage response = await client.GetAsync(StaticObjects.bungieBasePath + $#"/User/SearchUsers/?q={MembersName}");
if (response.IsSuccessStatusCode)
{
try
{
Console.WriteLine(await response.Content.ReadAsStringAsync());
dynamic content = response.Content.ReadAsAsync<ExpandoObject>().Result;
foreach (dynamic user in content.Response.results)
{
MembershipID.Add(user.membershipId);
}
}
catch
{
throw new ArgumentException("The member could not be found.");
}
}
else
{
throw new ArgumentException("An error occurred retrieving the members information.");
}
return MembershipID;
}
Command Code
[Command("invite")]
[RequireContext(ContextType.Guild, ErrorMessage = "This command is specific to a particular server so you must send it from a channel within that server")]
public async Task JoinDateAsync([Remainder]string MemberName)
{
using (Context.Channel.EnterTypingState())
if (!Context.IsPrivate) await Context.Message.DeleteAsync();
if (StaticObjects.CheckUserIsAdmin(Context))
{
List<string> MembershipID = await StaticObjects._bungie.GetMemberID(MemberName);
}
}
The first part all works fine and i can see the JSON Response in the console however when i try to pull out the "membershipId" i then get an error in the console and cannot figure out where i'm going wrong.
Any help would be appreciated.
I spotted my issue, the JSON response was slightly different. After i removed one word it now works.
From
foreach (dynamic user in content.Response.results)
To
foreach (dynamic user in content.Response)
Referring to this sample code on GitHub:
https://github.com/DblV/StreamingWebApi/blob/master/StreamingService/StreamingService/Controllers/StreamingController.cs
I want to stream content stored in a database, which my query returns as a sequence of blobs (essentially one file split into "blocks"). Due to the potential size of the complete response, I want to stream it, and I am following the above example as follows:
public class FileController : ApiController
{
[HttpGet]
public HttpResponseMessage Get(string id, [FromUri] string contentType)
{
var message = new HttpResponseMessage(HttpStatusCode.OK);
message.Content = new PushStreamContent((stream, content, context) =>
{
GetFileContent(stream, int.Parse(id));
Thread.Sleep(1000);
stream.Close();
});
message.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
return message;
}
private void GetFileContent(Stream stream, int id)
{
var result = Query(reader => reader.GetStream(0), id);
foreach (var b in result)
{
b.CopyToAsync(stream);
stream.Flush();
}
}
private IEnumerable<Stream> Query(Func<DbDataReader,Stream> func, int id)
{
var command = // Not shown - SELECT command creating the result set
var reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
yield return func(reader);
}
}
reader.Close();
}
Note the use and placement of Thread.Sleep. When I test this in the browser, the content downloads to a file, but without the Sleep, it hangs at the point of completion; with the Sleep, it completes properly, and the resulting download is perfect.
My question: what is the Sleep doing that averts the hang condition? My suspicion is that this is more of a work-around than a proper solution; if so, what should I be doing instead?
Found that it is a mistake on my part. I was incorrectly using CopyToAsync where I should be using CopyTo. Correcting this mistake, it works just fine.
i am trying to speed up some google directory api calls in the .net client library with BatchRequests
lets say i have the following batchRequest (which consists only of one
request for simplicity):
static async Task BatchRequesting()
{
var batchReq = new BatchRequest(_dirservices[0]);
var r = _dirservices[0].Users.Get("user#domain.com");
batchReq.Queue<UsersResource.GetRequest>(r,
(contentReq, error, j, message) =>
{
... what to do here?
});
await batchReq.ExecuteAsync();
}
how do i get the resulting deserialized response object in the callback (which would be a User object in my case)
Do i have to handle the message.Content object (HttpContent) myself with all the json deserializing?
I found the solution. I used the wrong generic parameter. My Code example has to be like this:
static async Task BatchRequesting()
{
var batchReq = new BatchRequest(_directoryService);
var request = _directoryService.Users.Get("user#domain.com");
batchReq.Queue<User>(request,
(returnedUser, error, j, message) =>
{
if (error != null)
{
Console.WriteLine(error.Message);
}
else
{
... work with returnedUser
}
});
await batchReq.ExecuteAsync();
}
With a little help from the performance tips in the Json.NET docs, I put together a method for downloading/deserializing JSON from a remote resource:
public async Task<T> GetJsonAsync<T>(string url)
{
using (var stream = await new HttpClient().GetStreamAsync(url))
{
using (var sr = new StreamReader(stream))
{
using (var jr = new JsonTextReader(sr))
{
return new JsonSerializer().Deserialize<T>(jr);
}
}
}
}
I'd like to have a non-generic version that returns a dynamic. Calling the above method with GetJsonAsync<dynamic>(url) works, until you try to access a dynamic property on the result, at which point I get:
'Newtonsoft.Json.Linq.JObject' does not contain a definition for '[MyProperty]'
I have seen how to deserialize to a dynamic from a string, but have not seen a working example of doing it directly from a stream, which would be preferable as it is more memory efficient. Is this possible?
It turns out this had little to do with Json.NET and more to do with my understanding of dynamics (which I rarely use). Thanks to #Peter Richie, I found that GetJsonAsync<dynamic> does work if I explicitly cast MyProperty to a string. But I'd rather not have to do that. Using my original method and a real working endpoint, here are 3 scenarios; only the last one works:
var url = "http://echo.jsontest.com/MyProperty/MyValue"; // great testing site!
var x1 = await GetJsonAsync<dynamic>(url);
Assert.AreEqual("MyValue", x1.MyProperty); // fail!
dynamic x2 = await GetJsonAsync<dynamic>(url);
Assert.AreEqual("MyValue", x2.MyProperty); // fail!
dynamic x3 = await GetJsonAsync<ExpandoObject>(url);
Assert.AreEqual("MyValue", x3.MyProperty); // pass!
Armed with that knowledge, the non-generic overload of my original method looks like this:
public async Task<dynamic> GetJsonAsync(string url) {
dynamic d = await GetJsonAsync<ExpandoObject>(url);
return d;
}
And users can do this:
var x = await GetJsonAsync(url);
Assert.AreEqual("MyValue", x.MyProperty); // pass!
It sounds like there's some information you haven't provided. The following works fine for me:
private T ReadJson<T>(Stream stream)
{
using (var reader = new StreamReader(stream))
{
using (var jr = new JsonTextReader(reader))
{
dynamic d = new JsonSerializer().Deserialize(jr);
return d;
}
}
}
//...
var d = ReadJson<dynamic>(new MemoryStream(Encoding.UTF8.GetBytes("{'MyProperty' : 'MyValue'}")));
Debug.WriteLine((String)d.MyProperty);