Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET HTTPClient Asynchronous Limitations

I have a small .Net 4.5 C# app which reads information from a data source and then pushes this information to a web site which is a .NET 4.5 Web API site with a simple controller. The controller receives the data and puts it into a database.

The following works for me, as fast as the application can read it can write and everything ends up in the DB:

    public static void PostDataToWebApi(MyDataClass tData)
    {
        HttpResponseMessage s = null;

        try
        {
            s = client.PostAsJsonAsync("/api/Station/Collector", tData).Result;
            s.EnsureSuccessStatusCode();
        }
        catch (Exception e)
        {
            Console.WriteLine("ERROR (ClientPost): " + e.ToString());
        }
    }

The following does NOT work. It POSTs about a thousand-odd records and then comes up with a number of errors all with the message "a task was canceled", but then after about 10 seconds it resumes processing:

    public static async void PostDataToWebApi(MyDataClass tData)
    {
        HttpResponseMessage s = null;

        try
        {
            s = await client.PostAsJsonAsync("/api/Station/Collector", tData);
            s.EnsureSuccessStatusCode();
        }
        catch (Exception e)
        {
            Console.WriteLine("ERROR (ClientPost): " + e.ToString());
        }
    }

The full error is:

    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
    at IICE_DataCollector_Remote.Program.<PostDataToWebApi>d__7.MoveNext() in e:\Users\TestUser.TEST\Documents\Visual Studio 2012\Projects\Test_App-trunk\TestCollector\Program.cs:line 475

Any quick fixes for this? From what I can tell it runs out of something, threads, sockets, who knows :-)

Any pointers would be appreciated, I'd love to get this working, as you can imagine doing the POST synchronously is considerably slower than asynchronously.

Just to be sure it wasn't my machine, local anti-virus or network I have tried on a W2k8 R2 server, a Windows 7 virtual guest desktop (fresh build) and a Windows 8 machine as well, with the same result.

More Info : I have tested this with partial success from a LAN connection with a smaller data set (10,000 records), and a DefaultConnectionLimit of 100. But, in production with 500,000 records, when posting to a remote server across the Internet (still low latency 25ms-50ms) I have not had any success.

Thanks in advance for any help :-)

like image 516
Dominik Avatar asked Aug 08 '13 07:08

Dominik


1 Answers

Ok, I have it working now. The biggest thing was to fine-tune the settings on the client end, for the server. These settings were different depending on whether I was running a test locally or over the Internet.

My "PostDataToWebApi" method now looks like this:

    public static async void PostDataToWebApi(MyDataClass tData)
        {

        await throttler.WaitAsync();
        allTasks.Add(Task.Run(async () =>
        {

            try
            {
                var s = await client.PostAsJsonAsync("/api/Station/Collector", tData).ConfigureAwait(false);
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR (ClientPost): " + e.ToString());
            }
            finally
            {
                throttler.Release();
            }
        }));
    }

I have the following declared at the top of my console application:

    private static List<Task> allTasks = new List<Task>();
    private static SemaphoreSlim throttler;

Before my loop starts I have the following, with the variables changed to make sure it all works:

    ServicePointManager.DefaultConnectionLimit = _DefaultConnections;
    ServicePointManager.MaxServicePointIdleTime = _MaxIdleTime;
    ServicePointManager.Expect100Continue = false;
    ServicePointManager.CheckCertificateRevocationList = false;

    throttle = new SemaphoreSlim(initialCount: _MaxQueue);

As a guide, for an Internet based transaction the following works for me:

  1. Default Connections : 24
  2. Max Idle Time : 400
  3. SemaphoreSlim Initial Count: 50

For my LAN test I could run both the default connections and the initial count values higher without a problem, which is to be expected I guess :-)

Finally, just outside my look I have the following to make sure I don't kill any tasks still running at the end of my execution run:

     await Task.WhenAll(allTasks);

Hope this helps!

like image 53
Dominik Avatar answered Oct 14 '22 00:10

Dominik