Strange execution when using async / await and System.Threading.Tasks.Parallel
I have the following method:
public async Task ExecuteAsync()
{
Task<IEnumerable<Comment>> gettingComments = RetrieveComments();
Dictionary<string, ReviewManager> reviewers = ConfigurationFacade.Repositories.ToDictionary(name => name, name => new ReviewManager(name));
IEnumerable<Comment> comments = await gettingComments;
Parallel.ForEach(reviewers, (reviewer) => {
Dictionary<Comment, RevisionResult> reviews = reviewer.Value.Review(comments);
int amountModerated = ModerateComments(reviews.Where(r => r.Value.IsInsult), "hide");
});
}
My method ModerateComments
looks like this:
private Task<int> ModerateComments(IEnumerable<Comment> comments, string operation)
{
return Task.Factory.StartNew(() =>
{
int moderationCount = 0;
Parallel.ForEach(comments, async (comment) =>
{
bool moderated = await ModerateComment(comment, operation); //Problem here
if(moderated)
moderationCount++;
}
return moderationCount;
};
}
Finally:
private async Task<bool> ModerateComment(Comment comment, string operation, string authenticationToken = null)
{
if(comment == null) return false;
if(String.IsNullOrWhiteSpace(authenticationToken))
authenticationToken = CreateUserToken(TimeSpan.FromMinutes(1));
string moderationEndpoint = ConfigurationFacade.ModerationEndpoint;
using(HttpRequestMessage request = new HttpRequestMessage())
{
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(moderationEndpoint);
using(HttpResponseMessage response = await _httpClient.SendAsync(request)) //Problem here
{
if(!response.IsSuccessStatusCode)
{
if(response.StatusCode == HttpStatusCode.Unauthorized)
return await ModerateComment(comment, operation, null); //Retry operation with a new access token
else if(response.StatusCode == HttpStatusCode.GatewayTimeout)
return await ModerateComment(comment, operation, authenticationToken); //Retry operation
return false;
}
}
}
return true;
}
I have a weird runtime problem. All of the above code works fine except when it reaches the line:
using(HttpResponseMessage response = await _httpClient.SendAsync(request)) {
//...
}
When I debug my application, this statement is executed, but immediately after that it does not throw any exception or return anything, it just ends up executing and I get the next statement in the loop Parallel.ForEach
.
I find it very difficult to explain, so I posted some images:
So far so good, I get to the next line of code:
Execution continues well and I get to the Moderation API call
Even if I press F10 (Next statement) in the debugger, the flow continues to the next loop in the Parallel.ForEach loop.
As you can see, I have breakpoints in try-catch, since I am throwing an exception, but the breakpoint is never triggered, and no breakpoint in if(moderacion) commentCount++
.
And what's going on here? Where did my thread of execution go? It just disappears after sending a POST request to the API.
After execution continues, all enum elements take the same jump, and so my variable commentCount
ends up being 0
source to share
You don't need Parallel.ForEach
or Task.Factory.StartNew
do the job with IO binding:
private async Task<int> ModerateCommentsAsync(IEnumerable<Comment> comments, string operation)
{
var commentTasks = comments.Select(comment => ModerateCommentAsync(comment, operation));
await Task.WhenAll(commentTasks);
return commentTasks.Count(x => x.Result);
}
The common practice is to add posttext Async
to the async method.
source to share