disjointed
disjointed

Reputation: 11

Http POST using Task.FromAsync with async request and async response

I have to send an async POST to a web service and I want to send/retrieve the data asynchronously.

I also need to send the request but only wait a maximum of 1500 ms waiting for a response. If I don't get a response, the program should continue on (this is a service making an external web service call). I want to offload these service calls to IOCP's instead of blocking for a long time and waiting for them to return. I only want to block for 1500 ms total.

Here's what I have so far:

 var httpRequest = (HttpWebRequest)WebRequest.Create(@"urltoPostTo");

            httpRequest.Method = "POST";
            byte[] data = Encoding.ASCII.GetBytes("test-post");
            httpRequest.ContentLength = data.Length;
            var asyncTask = Task.Factory.FromAsync<Stream>(httpRequest.BeginGetRequestStream, httpRequest.EndGetRequestStream, httpRequest)
                .ContinueWith(response =>
                {
                    var localStream = response.Result;
                    if (localStream != null)
                    {
                        localStream.Write(data, 0, data.Length);
                        localStream.Close();
                    }
                });  //how do I do the continuation for BeginGetResponse and EndGetResponse from here?

I have a couple of requirements that unfortunately I can't change.

  1. I am using Visual Studio 2010 targeting 4.0
  2. I cannot use the Async BCL
  3. I would like to try and use Task.FromAsync

Upvotes: 1

Views: 2046

Answers (3)

sh1ng
sh1ng

Reputation: 2973

Try this method helper.

public static Task<string> Post(string url, Encoding encoding, string content)
            {
                var httpRequest = (HttpWebRequest)WebRequest.Create(url);
                httpRequest.Method = "POST";
                byte[] data = encoding.GetBytes(content);
                httpRequest.ContentLength = data.Length;

                TaskCompletionSource<string> result = new TaskCompletionSource<string>();


                Task.Factory.FromAsync<Stream>(httpRequest.BeginGetRequestStream, httpRequest.EndGetRequestStream, httpRequest)
                   .ContinueWith(requestStreamTask =>
                   {
                       try
                       {
                           using (var localStream = requestStreamTask.Result)
                           {
                               localStream.Write(data, 0, data.Length);
                               localStream.Flush();
                           }

                           Task.Factory.FromAsync<WebResponse>(httpRequest.BeginGetResponse, httpRequest.EndGetResponse, httpRequest)
                               .ContinueWith(responseTask =>
                               {
                                   try
                                   {
                                       using (var webResponse = responseTask.Result)
                                       using (var responseStream = webResponse.GetResponseStream())
                                       using (var sr = new StreamReader(responseStream, encoding))
                                       {
                                           result.SetResult(sr.ReadToEnd());
                                       }
                                   }
                                   catch (Exception e)
                                   {
                                       result.SetException(e);
                                   }
                               }, TaskContinuationOptions.AttachedToParent);
                       }
                       catch (Exception e)
                       {
                           result.SetException(e);
                       }

                   }, TaskContinuationOptions.AttachedToParent);

                return result.Task;
            }

Upvotes: 0

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131581

This has already been answered in Implementing extension method WebRequest.GetResponseAsync with support for CancellationToken with a GetResponseAsync extension method that properly handles timeouts and CancellationTokens. When Request.Timeout expires, the method calls Request.Abort before returning the Task in its cancelled state.

The reason for such involved coding is that it's the client's (yours) responsibility to properly handle timeouts, so you can't depend on FromAsync to handle the timeout expiration. Perhaps this is the reason why FromAsync doesn't accept a cancellation token.

Another option is to avoid cancelling the request itself and cancel the continuation. You can use the ContinueWith overload that accepts a CancellationToken and call CancellationTokenSource.CancelAfter to set the cancellation timeout.

This would allow your code to ignore the results and keep running but it wouldn't break the connection to the server and wouldn't stop the background thread from processing any potentially expensive results.

You could write something like this:

var tcs=new CancellationTokenSource();
var asyncTask = Task.Factory.FromAsync<Stream>(httpRequest.BeginGetRequestStream, httpRequest.EndGetRequestStream, httpRequest)
.ContinueWith(response =>
{...},
cts.Token);  
cts.CancelAfter(1500);

Note that the call to CancelAfter is done after starting the asynchronous task.

I would prefer Reed Copsey's extension method in a busy site because a high number of cancelled but outstanding requests can easily exhaust the thread pool, consume a lot of memory without a reason and consume potentially expensive connections to external systems.

Upvotes: 1

li-raz
li-raz

Reputation: 1696

why not to work with HttpClient?

webApiHttpClient.PostAsJsonAsync(GetFullAPI("api/Subscribe"), obj)
            .ContinueWith(res => _logger.InfoFormat("Subscribe result: {0}", res.Result.StatusCode));

Upvotes: 0

Related Questions