I am calling this action (ASP.Net Core 2.0) over AJAX:
[HttpGet]
public async Task<IActionResult> GetPostsOfUser(Guid userId, Guid? categoryId)
{
var posts = await postService.GetPostsOfUserAsync(userId, categoryId);
var postVMs = await Task.WhenAll(
posts.Select(async p => new PostViewModel
{
PostId = p.Id,
PostContent = p.Content,
PostTitle = p.Title,
WriterAvatarUri = fileService.GetFileUri(p.Writer.Profile.AvatarId, Url),
WriterFullName = p.Writer.Profile.FullName,
WriterId = p.WriterId,
Liked = await postService.IsPostLikedByUserAsync(p.Id, UserId),// TODO this takes too long!!!!
}));
return Json(postVMs);
}
But it took too long to response (20 seconds!!!) in case I have many post
objects in posts
array (e.g. 30 posts).
That is caused because of this line await postService.IsPostLikedByUserAsync
.
Digging into the source code of this function:
public async Task<bool> IsPostLikedByUserAsync(Guid postId, Guid userId)
{
logger.LogDebug("Place 0 passed!");
var user = await dbContext.Users
.SingleOrDefaultAsync(u => u.Id == userId);
logger.LogDebug("Place 1 passed!");
var post = await dbContext.Posts
.SingleOrDefaultAsync(u => u.Id == postId);
logger.LogDebug("Place 2 passed!");
if (user == null || post == null)
return false;
return post.PostLikes.SingleOrDefault(pl => pl.UserId == userId) != null;
}
The investigations showed, after some seconds, ALL "Place 1 passed!" logging methods are executed together for every post
object. In other words, it seems that every post await
s until the previous post finishes executing this part:
var user = await dbContext.Users
.Include(u => u.PostLikes)
.SingleOrDefaultAsync(u => u.Id == userId);
And then -when every post finishes that part- the place 1 of log is executed for all post
objects.
The same happens for logging place 2, every single post seems to await for the previous post to finish executing var post = await dbContext.Pos...
, and then the function can go further to execute log place 2 (after few seconds from log 1, ALL log 2 appear together).
That means I have no asynchronous execution here. Could some one help me to understand and solve this problem?
UPDATE:
Changing the code a bit to look like this:
/// <summary>
/// Returns all post of a user in a specific category.
/// If the category is null, then all of that user posts will be returned from all categories
/// </summary>
/// <param name="userId"></param>
/// <param name="categoryId"></param>
/// <returns></returns>
[Authorize]
[HttpGet]
public async Task<IActionResult> GetPostsOfUser(Guid userId, Guid? categoryId)
{
var posts = await postService.GetPostsOfUserAsync(userId, categoryId);
var i = 0;
var j = 0;
var postVMs = await Task.WhenAll(
posts.Select(async p =>
{
logger.LogDebug("DEBUG NUMBER HERE BEFORE RETURN: {0}", i++);
var isLiked = await postService.IsPostLikedByUserAsync(p.Id, UserId);// TODO this takes too long!!!!
logger.LogDebug("DEBUG NUMBER HERE AFTER RETURN: {0}", j++);
return new PostViewModel
{
PostId = p.Id,
PostContent = p.Content,
PostTitle = p.Title,
WriterAvatarUri = fileService.GetFileUri(p.Writer.Profile.AvatarId, Url),
WriterFullName = p.Writer.Profile.FullName,
WriterId = p.WriterId,
Liked = isLiked,
};
}));
return Json(postVMs);
}
That shows, that this line "DEBUG NUMBER HERE AFTER RETURN" is printed for ALL select
methods together, that means that ALL select
methods waits for each other before going further, how can I prevent that?
UPDATE 2
Substituting the previous IsPostLikedByUserAsync
method, with the following one:
public async Task<bool> IsPostLikedByUserAsync(Guid postId, Guid userId)
{
await Task.Delay(1000);
}
Showed no problem in async running, I had to wait only 1 second, not 1 x 30. That means it is something specific to EF.
Why does the problem happen ONLY with entity framework (with the original function)? I notice the problem even with only 3 post
objects! Any new ideas?