简体   繁体   English

使用HTTPClient在foreach中进行异步/等待

[英]Async/Await in foreach with HTTPClient

I have a webservice that loads up some plugins (dlls) and calls their Process method. 我有一个Web服务,可加载一些插件(dll)并调用其Process方法。 One of the plugins takes a list of members and ensures that they are all included in a MailChimp list. 其中一个插件采用成员列表,并确保它们都包含在MailChimp列表中。

Here is the code that adds the users to the MailChimp group. 这是将用户添加到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));

            }
        }
    }

In normal procedural code, this wouldn't be a problem. 在普通的程序代码中,这不会有问题。 However, the only Post method available on the httpClient was PostAsync. 但是,httpClient上唯一可用的Post方法是PostAsync。 Being fairly new to the async/await stuff, I'm not sure the ramifications on the rest of my code ... particularly as it relates to my attempt to reuse the httpClient instead of instantiating a new one for each http call. 作为异步/等待对象的新手,我不确定其余代码的后果……尤其是它与我尝试重用httpClient而不是为每个http调用实例化一个新实例有关。

I'm not sure what happens with await when its wrapped in a foreach like I have. 我不确定当它像我一样包裹在foreach中时,等待会发生什么。 Will I run into issues with reusing the httpClient to make repeated calls when running asynchronously? 在异步运行时,重用httpClient进行重复调用会遇到问题吗?

My other question is, what is actually going to be returned. 我的另一个问题是,实际上将返回什么。 IOW, my understanding is that await returns a Task. IOW,我的理解是等待返回一个Task。 However, here, I'm looping through the list and making multiple calls to await PostAsync. 但是,在这里,我遍历列表并进行多次调用以等待PostAsync。 My method returns a Task. 我的方法返回一个任务。 But which task gets returned? 但是返回哪个任务? If my calling method needs to wait for completion before moving on, what does its call look like? 如果我的调用方法需要等待完成才能继续,它的调用是什么样的?

private void Process()
{
    //Get List

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

    //Subsequent processing
 }

I've read that you should use Async all the way. 我读过您应该一路使用Async。 Does this mean my calling method should look more like this? 这是否意味着我的调用方法应更像这样?

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

    //Other processing
  }

Thanks to whatever help you can offer on this. 感谢您可以为此提供的任何帮助。

In normal procedural code, this wouldn't be a problem. 在普通的程序代码中,这不会有问题。

The whole point of async / await is to write asynchronous code in a way that looks practically identical to "normal" synchronous code. async / await的全部要点是以与“常规”同步代码几乎相同的方式编写异步代码。

Being fairly new to the async/await stuff, I'm not sure the ramifications on the rest of my code ... particularly as it relates to my attempt to reuse the httpClient instead of instantiating a new one for each http call. 作为异步/等待对象的新手,我不确定其余代码的后果……尤其是它与我尝试重用httpClient而不是为每个http调用实例化一个新实例有关。

HttpClient was intended to be reused; HttpClient打算被重用; in fact, it can be used for any number of calls simultaneously. 实际上,它可以同时用于任意数量的呼叫。

I'm not sure what happens with await when its wrapped in a foreach like I have. 我不确定当它像我一样包裹在foreach中时,等待会发生什么。

One way to think of it is that await "pauses" the method until its operation completes. 考虑它的一种方法是await “暂停”该方法,直到其操作完成。 When the operation completes, then the method continues executing. 操作完成后,该方法将继续执行。 I have an async intro that goes into more detail. 我有一个async介绍 ,其中有更详细的介绍。

Will I run into issues with reusing the httpClient to make repeated calls when running asynchronously? 在异步运行时,重用httpClient进行重复调用会遇到问题吗?

No, that's fine. 不,很好。

IOW, my understanding is that await returns a Task. IOW,我的理解是等待返回一个Task。

await takes a Task . await 需要一个Task It "unwraps" that task and returns the result of the task (if any). 它“解包”该任务并返回任务的结果(如果有)。 If the task completed with an exception, then await raises that exception. 如果任务完成但有异常,则await引发该异常。

My method returns a Task. 我的方法返回一个任务。 But which task gets returned? 但是返回哪个任务?

The Task returned from an async method is created by the async state machine. async方法返回的Taskasync状态机创建。 You don't have to worry about it. 您不必担心。 See my intro for more details. 有关更多详细信息,请参见我的介绍。

If my calling method needs to wait for completion before moving on, what does its call look like? 如果我的调用方法需要等待完成才能继续,它的调用是什么样的? ... I've read that you should use Async all the way. ...我读到你应该一直使用异步。 Does this mean my calling method should look more like this? 这是否意味着我的调用方法应更像这样?

Yes, it should look like your second snippet: 是的,它应该看起来像您的第二个片段:

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

  //Other processing
}

The only thing I changed was the Async suffix, which is recommended by the Task-based Asynchronous Pattern . 我唯一更改的是Async后缀,这是基于任务的异步模式推荐的

in your code you should be fine with reusing the HttpClient. 在您的代码中,重新使用HttpClient应该没问题。 What async / await is allow the code to release the execution thread to prevent locking a cpu thread while waiting for the web response. 异步/等待是允许代码释放执行线程,以防止在等待Web响应时锁定cpu线程。 It also releases control back to the caller. 它还将控制权释放回调用者。 When releasing code back to the caller it means that if your Process function does not await your AddMCUsers , Process could finish before AddMCUsers (useful in fire and forget situations to not await a method). 当将代码释放回调用方时,这意味着如果您的Process函数不等待您的AddMCUsers ,则Process可以在AddMCUsers之前AddMCUsers (在发生火灾时AddMCUsers用,并且会忘记不要等待方法)。

What async/await do not do is affect the logical flow of an individual method. async/await不执行的操作会影响单个方法的逻辑流程。 When you await an async web call the execution is paused and then resumed at the same point once the web call returns. 当您等待异步Web调用时,将暂停执行,然后在Web调用返回时在同一点恢复执行。 There is also thread context tracking and the code resumes in the same context (ie. UI thread or background thread depending on the parent) by default, but this can be changed if needed. 还存在线程上下文跟踪,并且默认情况下,代码在相同的上下文(即,取决于父对象的UI线程或后台线程)中恢复,但是可以根据需要进行更改。

At some point in your code you may want to have a method that blocks until your async code competes and that is where you will want your Task.Wait() call to block execution. 在您的代码中的某个时候,您可能希望有一个方法可以阻塞,直到异步代码竞争为止,这就是您要Task.Wait()调用阻塞执行的地方。 If all you use is awaits then it is possible for your program to end before your task competes. 如果等待使用,则程序可能在任务竞争之前结束。 See the code example below. 请参见下面的代码示例。

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

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

in the sample with out a Task.Wait call to block the Main method the program will end before the 5 second wait is complete. 在示例中,没有Task.Wait调用来阻塞Main方法,程序将在等待5秒之前结束。 Having a main method of the following will cause the program to wait for 5 seconds before exiting: 具有以下主要方法将导致程序等待5秒钟后退出:

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

    waitForMe.Wait();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM