2

I have made a class to handle multiple HTTP GET requests. It looks something like this:

public partial class MyHttpClass : IDisposable
{
    private HttpClient theClient;
    private string ApiBaseUrl = "https://example.com/";

    public MyHttpClass()
    {

        this.theClient = new HttpClient();
        this.theClient.BaseAddress = new Uri(ApiBaseUrl);
        this.theClient.DefaultRequestHeaders.Accept.Clear();
        this.theClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    public async Task<JObject> GetAsync(string reqUrl)
    {

        var returnObj = new JObject();
        var response = await this.theClient.GetAsync(reqUrl);

        if (response.IsSuccessStatusCode)
        {
            returnObj = await response.Content.ReadAsAsync<JObject>();
            Console.WriteLine("GET successful");

        }
        else
        {
            Console.WriteLine("GET failed");
        }

        return returnObj;
    }
    public void Dispose()
    {
        theClient.Dispose();
    }
}

I am then queueing multiple requets by using a loop over Task.Run() and then after Task.WaitAll() in the manner of:

public async Task Start()
{
    foreach(var item in list)
    {
        taskList.Add(Task.Run(() => this.GetThing(item)));
    }
    Task.WaitAll(taskList.ToArray());
}

public async Task GetThing(string url)
{
    var response = await this.theClient.GetAsync(url);

    // some code to process and save response

}

It definitiely works faster than synchonus operation but it is not as fast as I expected. Based on other advice I think the local threadpool is slowing me down. MSDN suggest I should specify it as a long running task but I can't see a way to do that calling it like this.

Right now I haven't got into limiting threads, I am just doing batches and testing speed to discover the right approach.

Can anyone suggest some areas for me to look at to increase the speed?

Guerrilla
  • 13,375
  • 31
  • 109
  • 210

2 Answers2

4

So, after you've set your DefaultConnectionLimit to a nice high number, or just the ConnectionLimit of the ServicePoint that manages connections to the host you are hitting:

ServicePointManager
    .FindServicePoint(new Uri("https://example.com/"))
    .ConnectionLimit = 1000;

the only suspect bit of code is where you start everything...

public async Task Start()
{
    foreach(var item in list)
    {
        taskList.Add(Task.Run(() => this.GetThing(item)));
    }
    Task.WaitAll(taskList.ToArray());
}

This can be reduced to

var tasks = list.Select(this.GetThing);

to create the tasks (your async methods return hot (running) tasks... no need to double wrap with Task.Run)

Then, rather that blocking while waiting for them to complete, wait asynchronously instead:

await Task.WhenAll(tasks);
spender
  • 117,338
  • 33
  • 229
  • 351
  • Setting servicepoint manager and changing to WhenAll() has given a performance increase. Will do some larger batches tomorrow to see just how much. If I unwrap the tasks like you suggested then it loads all the requests in the same thread. I need to do larger batches to work out if that's better or not. On a couple of runs like that with tasks all announcing same thread name I got some GET failures and it hung but it may have been unrelated (to be clear they start at same time but in the same thread ID whereas other way there are multiple thread IDs in use). – Guerrilla May 11 '15 at 01:46
  • @guerrilla Yes, the first part of an async method (before you hit the first await) runs synchronously. This is normal. You don't need to code around this. – spender May 11 '15 at 01:57
0

You are probably hitting some overhead in creating multiple instance-based HttpClient vs using a static instance. Your implementation will not scale. Using a shared HttpClient is actually recommended.

See my answer why - What is the overhead of creating a new HttpClient per call in a WebAPI client?

Dave Black
  • 7,305
  • 2
  • 52
  • 41