Asynchronous method using .Result

强颜欢笑 提交于 2019-12-20 04:36:09

问题


The following method loops through a list of postdata to make multiple requests for a token, for example each request uses a specific clientID. My question relates to async. I'm trying to make the calls for the token(s) to be async. Does the use of .Result necessarily make the method synchronous?

public async Task<string> ReturnDataFromUrl1(List<List<KeyValuePair<string, string>>> listOfPostData)
{        
   List<Task<string>> taskList = new List<Task<string>>();       
   string allTokens = "";
   List<Task<string>> downloadTasks = new List<Task<string>>();


   foreach (var postData in listOfPostData)
   {
       using (var client = new HttpClient())
       {
           client.BaseAddress = new Uri("http://localhost:23081");

           HttpContent httpContent = new FormUrlEncodedContent(postData);
           HttpResponseMessage response = client.PostAsync("/Token", httpContent).Result;

           var responsecode = (int)response.StatusCode;

           if (response.IsSuccessStatusCode)
           {
               var responseBodyAsText = response.Content.ReadAsStringAsync();

               taskList.Add(responseBodyAsText);
           }
       }
   }

    downloadTasks = taskList.ToList();            

    while (downloadTasks.Count > 0)
    {
        Task<string> firstFinishedTask = await Task.WhenAny(downloadTasks);

        downloadTasks.Remove(firstFinishedTask);

        // Await the completed task. 
        string content = await firstFinishedTask;

        allTokens = allTokens + content;
    }

    return allTokens;
}

回答1:


Does the use of .Result necessarily make the method synchronous?

It will make the part which blocks with .Result synchronous, as it synchronously waits for the completion of the Task (If your code doesn't deadlocking, which is highly possible in an environment with a custom SynchronizationContext).

This method invocation:

Task<string> firstFinishedTask = await Task.WhenAny(downloadTasks);

Will be asynchronous as it will yield control until the first stream is read and converted to a string. If you already have a method marked as async, simply await that part as well:

HttpResponseMessage response = await client.PostAsync("/Token", httpContent);

Side Note:

I think I would of taken a different approach to the problem. Generally, it's the network IO which will consume the most time for this method. If possible and given no limitation, I'd concurrently make those network calls, and then process the results as they come:

public async Task<string> ReturnDataFromUrlAsync(
                                List<List<KeyValuePair<string, string>>> listOfPostData)
{
    var client = new HttpClient
    {
        BaseAddress = new Uri("http://localhost:23081")
    };

    var downloadTasks = listOfPostData.Select(postData =>
    {
        var content = new FormUrlEncodedContent(postData);
        return client.PostAsync("/Token", content);
    }).ToList();

    var tokenBuilder = new StringBuilder(downloadTasks.Count);
    while (downloadTasks.Count > 0)
    {
        var finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        var response = await finishedTask;

        if (!response.IsSuccessStatusCode)
            continue;

        var token = await response.Content.ReadAsStringAsync();
        tokenBuilder.Append(token);
    }

    return tokenBuilder.ToString();
}

Or since you need all of the results in order to process the the token anyway, you can use Task.WhenAll to wait on all of them to complete:

public async Task<string> ReturnDataFromUrlAsync(
                                List<List<KeyValuePair<string, string>>> listOfPostData)
{
    var client = new HttpClient
    {
        BaseAddress = new Uri("http://localhost:23081")
    };

    var downloadTasks = listOfPostData.Select(postData =>
    {
        var content = new FormUrlEncodedContent(postData);
        return client.PostAsync("/Token", content);
    });

    HttpResponseMessage[] response = await Task.WhenAll(downloadTasks);

    var tokenBuilder = new StringBuilder(response.Length);
    foreach (var element in response.Where(message => message.IsSuccessStatusCode))
    {
        tokenBuilder.Append(await element.Content.ReadAsStringAsync());
    }
    return tokenBuilder.ToString();
}


来源:https://stackoverflow.com/questions/33970665/asynchronous-method-using-result

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!