使用HTTPClient异步/等待foreach

时间:2015-06-01 21:57:12

标签: c# asynchronous async-await

我有一个web服务,可以加载一些插件(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返回一个Task。但是,在这里,我循环遍历列表并进行多次调用以等待PostAsync。我的方法返回一个Task。但哪个任务返回?如果我的调用方法需要在继续之前等待完成,它的调用是什么样的?

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
  }

感谢您提供的任何帮助。

2 个答案:

答案 0 :(得分:4)

  

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

WHERE line.`The #` IS NOT NULL AND line.`Person's First/Last Name` IS NOT NULL / async的重点是以与“普通”同步代码几乎完全相同的方式编写异步代码。

  

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

await旨在重复使用;实际上,它可以同时用于任意数量的呼叫。

  

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

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

  

在异步运行时,重新使用httpClient重复调用会遇到问题吗?

不,那很好。

  

IOW,我的理解是等待返回一个任务。

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

  

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

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

  

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

是的,它看起来应该是你的第二个片段:

async

我唯一更改的是public async Task ProcessAsync() { //Get list ... await AddMCUsers(list); //Other processing } 后缀,Task-based Asynchronous Pattern推荐使用。{/ p>

答案 1 :(得分:0)

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

async/await不做的是影响单个方法的逻辑流程。当您等待异步Web调用时,执行暂停,然后在Web调用返回后在同一点恢复。还有线程上下文跟踪,默认情况下代码在相同的上下文中恢复(即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秒等待完成之前结束。拥有以下主要方法将导致程序在退出前等待5秒钟:

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

    waitForMe.Wait();
}