5

The goal of the method below is to asynchronously set up and fire an http post from a desktop app to a web controller. I think that there must be a problem with how we set up the Task below, and I believe there are better practices available in .NET 4.5 such as async/await and Task.Run that would address the problem, but upgrading is not currently an option. Is there a better way to handle/write this in .NET 4.0, to prevent the problems described below?

    public void PostWithoutResponse(object objectToPost, string url) {
        Task.Factory.StartNew(() =>
        {
            using (var handler = new HttpClientHandler()) {
                handler.PreAuthenticate = true;
                handler.Credentials = _credentialPool.GetNetworkCredentials(new Uri(url));
                using (var client = new HttpClient(handler)) {
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                    using (var stringContent = new StringContent(JsonConvert.SerializeObject(objectToPost), Encoding.UTF8, "application/json")) {
                        // We weren't able to get this post to work without waiting for result
                        var result = client.PostAsync(url, stringContent).Result;
                    }
                }
            }
        });
    }

That method will sometimes work only 2-3 times, sometimes several, and sometimes it even works for hundreds of posts -- several batches -- before failing. The program continues, but no further posts are reflected in the database, and eventually an exception is thrown. (Possibly due to a timeout.)

We were able to observe this is the exception being thrown:

System.AggregateException was unhandled
Message: An unhandled exception of type 'System.AggregateException' occurred in mscorlib.dll
Additional information: One or more errors occurred.

With one inner exception:

_innerException {"The request was canceled"}    System.Exception {System.Net.WebException}

Interestingly, although the database updates stop after 2-3, the program (an automated batch-processing workflow) continues running and the exception doesn't seem to be thrown until this method is reached to get a new batch. Possibly related?

    public string GetPostResult(object objectToPost, string url) {
        string jsonResult = null;
        using (var handler = new HttpClientHandler()) {
            handler.PreAuthenticate = true;
            handler.Credentials = _credentialPool.GetNetworkCredentials(new Uri(url));
            using (var client = new HttpClient(handler)) {
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var serializedContent = JsonConvert.SerializeObject(objectToPost);
                using (var stringContent = new StringContent(serializedContent, Encoding.UTF8, "application/json")) {
                    var taskResult = client.PostAsync(url, stringContent).Result;
                    jsonResult = taskResult.Content.ReadAsStringAsync().Result;
                }
            }
        }
        return jsonResult;
    }

Also I should mention that we've tried putting try/catch in several arrangements, but can't seem to capture that exception anywhere until it bubbles out and breaks the runtime.

(Initially it seemed that the code above was working on our development computers, and failing on production machines, but that turns out to have been random luck combined with a misunderstanding on our part.)

Zword
  • 6,605
  • 3
  • 27
  • 52
RJB
  • 2,063
  • 5
  • 29
  • 34
  • Sounds like you might be hitting the server too quickly and are being rate-limited. – Steve Czetty Jan 21 '14 at 18:49
  • Appreciate the thought, but wouldn't explain why it works fine on our machines at the same rate though. – RJB Jan 21 '14 at 18:53
  • @RJB because IIS set up on your machines do not have the rate limiting enabled? – Scott Chamberlain Jan 21 '14 at 18:56
  • The fat client is hitting a prod server's mvc controller, not localhost.... Unless I'm not understanding something about how IIS works on our machines, which is possible. – RJB Jan 21 '14 at 19:01
  • 1
    see also http://stackoverflow.com/questions/2459241/httpwebrequest-the-request-was-aborted-the-request-was-canceled – Peter Ritchie Feb 03 '14 at 21:11
  • @RJB, are you experiencing this with the latest HttpClient: http://www.nuget.org/packages/Microsoft.Net.Http ? – noseratio Feb 03 '14 at 22:33
  • @Noseratio I'm using System.Net.Http for the HttpClient, version 2.0.0.0 I think. Anything that's not already in our internal nuget sources requires a paperwork-battle to access. – RJB Feb 03 '14 at 22:48
  • 1
    Then maybe you should consider the plain old `WebRequest` and set its `webRequest.KeepAlive = false`, as shown via the link by @PeterRitchie. You're not taking advantage of async `HttpClient` methods anyway in your existing code. – noseratio Feb 03 '14 at 22:53

1 Answers1

2

This code seems to have solved it.....

    public void PostWithoutResponse(object objectToPost, string url) {
        var uri = new Uri(url);
        var httpPost = (HttpWebRequest)WebRequest.Create(uri);
        httpPost.KeepAlive = false;
        httpPost.Method = "POST";
        httpPost.Credentials = _credentialPool.GetNetworkCredentials(uri);
        httpPost.ContentType = "application/json";
        using (var streamWriter = new StreamWriter(httpPost.GetRequestStream())) {
            var json = JsonConvert.SerializeObject(objectToPost);
            streamWriter.Write(json);
            streamWriter.Flush();
            streamWriter.Close();
        }
        Task.Factory.StartNew(() => httpPost.GetResponse());
    }
RJB
  • 2,063
  • 5
  • 29
  • 34
  • RJB, are you sure the last line shouldn't be just `return httpPost.GetResponse()`, without `Task.Factory.StartNew`? – noseratio Feb 05 '14 at 09:59
  • Tried it that way first, the method was blocking synchronously on '.GetResponse()', this is a more convenient fire-and-forget. It's worked for several thousand posts so far. – RJB Feb 05 '14 at 17:51
  • .... although an argument could be made, maybe, for `Task.Factory.FromAsync(httpPost.BeginGetResponse, httpPost.EndGetResponse, null);` Hmmm. – RJB Feb 05 '14 at 18:17
  • Yes, you can use `FromAsync` and even add some retry logic: http://stackoverflow.com/a/21346870/1768303, but also you should almost never do fire-and-forget, because your desktop app will not be able to observe any errors. – noseratio Feb 05 '14 at 20:59