【问题标题】:Async/Await in foreach with HTTPClient使用 HTTPClient 在 foreach 中进行异步/等待
【发布时间】:2015-06-03 03:51:15
【问题描述】:

我有一个加载一些插件(dll)并调用它们的 Process 方法的网络服务。其中一个插件获取成员列表并确保它们都包含在 MailChimp 列表中。

这是将用户添加到 MailChimp 组的代码。

    private async Task AddMCUsers(List<Member> _memberList)
    {
        using (var http = new HttpClient())
        {
            var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user:password");
            http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", creds);
            string memberURI = string.Format(@"{0}lists/{1}/members", _baseURI, _memberGroupId);
            var jss = new JavaScriptSerializer();

            foreach (var user in _memberlist)
            {
                var _addStatus = "";

                try
                {
                    var content = jss.Serialize(new MCPost()
                    {
                        email_address = user.Email,
                        status = "subscribed",
                        merge_fields = new MCMergeFields()
                        {
                            FNAME = user.Firstname,
                            LNAME = user.Lastname
                        }
                    });

                    using(var result = await http.PostAsync(memberURI, new StringContent(content,Encoding.UTF8, "application/json")))
                    {
                        var resultText = await result.Content.ReadAsStringAsync();

                        if(result.IsSuccessStatusCode)
                        {
                            _addStatus = "Success";
                            var _returnedUser = jss.Deserialize<MCMember>(resultText);
                            //Store new user's id
                            user.ServiceId = _returnedUser.id;
                        }
                        else
                        {
                            _addStatus = "Fail";
                        }
                    }
                }
                catch {
                    _addStatus = "Error";
                }

                LogEvent("Add User - " + _addStatus, string.Format("Id: {0} - {1} {2} (Account: {3}) : {4}", user.Id, user.Firstname, user.Lastname, user.AccountId, user.Email));

            }
        }
    }

在正常的程序代码中,这不是问题。但是,httpClient 上唯一可用的 Post 方法是 PostAsync。作为 async/await 的新手,我不确定对我的其余代码的影响......特别是因为它与我尝试重用 httpClient 而不是为每个 http 调用实例化一个新的尝试有关。

我不确定当它像我一样被包裹在一个 foreach 中时会发生什么。在异步运行时我会遇到重用 httpClient 重复调用的问题吗?

我的另一个问题是,实际上将返回什么。 IOW,我的理解是等待返回一个任务。但是,在这里,我正在遍历列表并多次调用 await PostAsync。我的方法返回一个任务。但是哪个任务会被返回?如果我的调用方法需要等待完成才能继续,它的调用是什么样的?

private void Process()
{
    //Get List

    var task = AddMCUsers(list);
    task.Wait();

    //Subsequent processing
 }

我了解到您应该一直使用 Async。这是否意味着我的调用方法应该看起来更像这样?

 public async Task Process()
 {
    //Get list
    ...
    await AddMCUsers(list);

    //Other processing
  }

感谢您为此提供的任何帮助。

【问题讨论】:

  • 只是一个快速的建议——请永远不要使用catch { ... }catch (Exception ex) { ... }。这只是一个非常糟糕的习惯,它会吞噬错误,使您的代码难以调试。

标签: c# asynchronous async-await


【解决方案1】:

在正常的程序代码中,这不是问题。

async/await 的全部意义在于以一种看起来几乎与“正常”同步代码相同的方式编写异步代码。

对于 async/await 的东西相当陌生,我不确定对我的其余代码的影响......特别是因为它与我尝试重用 httpClient 而不是为每个 http 调用实例化一个新的有关.

HttpClient 旨在重复使用;事实上,它可以同时用于任意数量的调用。

我不确定当 await 像我一样包裹在 foreach 中时会发生什么。

一种思考方式是await“暂停”该方法,直到其操作完成。当操作完成时,该方法继续执行。我有一个async intro,它更详细。

在异步运行时我会遇到重用 httpClient 重复调用的问题吗?

不,没关系。

IOW,我的理解是await返回一个Task。

await 接受Task。它“解开”该任务并返回任务的结果(如果有)。如果任务完成时出现异常,则await 会引发该异常。

我的方法返回一个任务。但返回的是哪个任务?

async 方法返回的Taskasync 状态机创建。你不必担心。有关详细信息,请参阅我的介绍。

如果我的调用方法需要等待完成才能继续,它的调用是什么样的? ...我读过你应该一直使用 Async 。这是否意味着我的调用方法应该看起来更像这样?

是的,它应该看起来像你的第二个 sn-p:

public async Task ProcessAsync()
{
  //Get list
  ...
  await AddMCUsers(list);

  //Other processing
}

我唯一改变的是Async后缀,这是Task-based Asynchronous Pattern推荐的。

【讨论】:

  • 感谢您的回答!还有一个问题,我的编码方式似乎是,HTTPClient 调用都在相互等待,而不是同时运行。 IOW,我的代码 - await http.PostAsync(...) 在继续 foreach 之前等待调用 ...对吗?所以在第一个完成之前它不能进行进一步的 HTTP 调用?有没有更好的方法来处理它,使它们能够同时运行......或并行运行——仍然无法理解差异——同时运行是我所追求的。
  • @RHarris:当然!首先,将foreach 循环的内部因素分解为添加单个用户(AddMCUserAsync)的单独方法,然后您可以将foreach 循环替换为await Task.WhenAll(_memberlist.Select(x =&gt; AddMCUserAsync(x, ...)));
【解决方案2】:

在您的代码中,您应该可以重用 HttpClient。什么 async / await 允许代码释放执行线程以防止在等待 Web 响应时锁定 cpu 线程。它还将控制权释放回调用者。当将代码释放回调用者时,这意味着如果您的Process 函数没有等待您的AddMCUsersProcess 可以在AddMCUsers 之前完成(在火灾和忘记不等待方法的情况下很有用)。

async/await 不会影响单个方法的逻辑流程。当您等待异步网络调用时,执行会暂停,然后在网络调用返回后在同一点恢复。还有线程上下文跟踪,默认情况下代码在相同的上下文中恢复(即 UI 线程或后台线程,取决于父级),但如果需要可以更改。

在您的代码中的某个时刻,您可能希望有一个方法在异步代码竞争之前阻塞,这就是您希望 Task.Wait() 调用阻塞执行的地方。如果您使用的只是等待,那么您的程序可能会在您的任务完成之前结束。请参阅下面的代码示例。

class Program
{
    static void Main(string[] args)
    {
        Task waitForMe = Task.Run(() => waitAsync());
    }

    static async Task waitAsync()
    {
        await Task.Delay(5000);
    }
}

在没有调用Task.Wait 来阻止 Main 方法的示例中,程序将在 5 秒等待完成之前结束。拥有以下 main 方法会导致程序在退出前等待 5 秒:

static void Main(string[] args)
{
    Task waitForMe = Task.Run(() => waitAsync());

    waitForMe.Wait();
}

【讨论】:

    猜你喜欢
    • 2015-07-27
    • 2023-03-30
    • 1970-01-01
    • 2019-02-10
    • 2019-07-29
    • 2021-08-13
    • 2019-06-29
    • 2018-01-27
    相关资源
    最近更新 更多