【问题标题】:Strange execution jump when using async/await and System.Threading.Tasks.Parallel使用 async/await 和 System.Threading.Tasks.Parallel 时出现奇怪的执行跳转
【发布时间】:2015-06-29 19:31:39
【问题描述】:

我有以下方法:

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");
     });
}

我的ModerateComments 方法如下所示:

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;
      };
}

最后:

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;
}

我在运行时遇到了一个奇怪的问题。上述所有代码都可以正常工作,除非它到达该行:

using(HttpResponseMessage response = await _httpClient.SendAsync(request)) {
      //...
}

当我调试我的应用程序时,这条指令会被执行,但在那之后,它不会抛出任何异常,也不会返回任何东西,它只是完成了执行,我会派生到 Parallel.ForEach 循环上的下一条语句。

这真的很难解释,所以我会发布一些图片:

到目前为止一切顺利,我到达了以下代码行:

执行进展顺利,我到达了对 Moderation API 的调用

即使我在调试器中按 F10(下一个语句),执行流程也会跳转到 Parallel.ForEach 循环中的下一个循环。

如你所见,我在 try-catch 中有断点,以防万一抛出任何异常,但断点从未激活,if(moderacion) commentCount++ 中的断点也没有激活。

那么这里会发生什么?我的执行流程去哪儿了?它只是在向 API 发送 POST 请求后消失。

继续执行后,枚举中的所有元素都进行相同的跳转,因此,我的commentCount变量最终等于0

【问题讨论】:

    标签: c# .net multithreading asynchronous async-await


    【解决方案1】:

    您不需要 Parallel.ForEachTask.Factory.StartNew 来执行 IO 绑定工作:

    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);
    }
    

    通常的做法是将Async 后缀添加到异步方法中。

    【讨论】:

    • 这真的很有帮助!谢谢!
    • 我从未想过使用Select 来获取任务集合。通常我只是在将它们转储到WhenAll 调用之前手动将它们添加到集合中。聪明。
    • @Johnathan Select 当您想要投影集合中的所有值时很好
    【解决方案2】:

    对常见问题的出色描述。 Parallel.ForEach 不支持异步 lambda。异步方法在遇到需要阻塞的第一个等待时返回。当您发出 HTTP 请求时会发生这种情况。

    对并行异步 foreach 循环使用一种常见模式。

    【讨论】:

    • 哇,这确实很有趣!感谢您的快速回复
    猜你喜欢
    • 1970-01-01
    • 2019-10-05
    • 1970-01-01
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多