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: Image01

Execution continues well and I get to the Moderation API call Image02

Even if I press F10 (Next statement) in the debugger, the flow continues to the next loop in the Parallel.ForEach loop.

Image03

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 Image04

+3


source to share


2 answers


An excellent description of a common problem. Parallel.ForEach does not support async lambdas. async methods return as soon as they hit the first wait to be blocked. This happens when you send an HTTP request.



Use one of the common patterns for a parallel async foreach loop.

+3


source


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.

+4


source







All Articles