HttpClient - Lifetime Intent?

Topics: Web Api
Nov 1, 2011 at 8:18 AM

Hi All,

This may be a complete noob question, however, what is the intended lifetime of an HttpClient instance?

In other words, should a new instance of the HttpClient be created on a per call/request basis or 'could' it be used for a longer period with multiple calls to Get/Put etc. such as for the duration of some session (where session is a medium term period, perhaps not an entire application lifetime) for a particular View/ViewModel lifetime in WPF application.

I'm not seeing much guidance on its specific use pattern and many of the examples are too simple to require a more complex use of the class.

Thanks in advance!

Coordinator
Nov 1, 2011 at 9:31 AM

HttpClient can be reused across calls. It does not hold state about the current message. The thing you want to be careful of is making sure you dispose the messages themselves.

Nov 1, 2011 at 9:42 AM

Thanks Glenn,

To the point and clear!  Also, thanks for the heads-up on message disposal.

Nov 1, 2011 at 1:32 PM

Glenn,

One variant on this question is, can these calls be made simultaneously from different threads (tasks)?

I'm assuming from your statement above that there would be no shared state or resources used during requests/calls.
Is my assumption correct? 

I see in the implementation of HttpClient fields that look like they may not be thread safe (e.g. operationStarted, pendingRequestsCts, etc.) which makes me think this may be an issue.

Also, once a request is cancelled can further calls/requests be made of should the HttpClient be disposed and recreated? 

Lastly, is there a good example of HttpClient being used accross calls and possibly in a multi-threaded way.

I'm starting to feel like using an HttpClient across calls is not a wise pattern to follow!

Thanks!

Nov 1, 2011 at 1:43 PM

I would not recommend using HttpClient across threads, but I don't see an issue with using it again after a cancelled request.  Out of curiosity, why are you trying to use the client across threads?  Is it to avoid re-configuring the default headers and the HttpClientHandler/WebRequestHandler?

Nov 1, 2011 at 2:07 PM

Hi Darrel,

Perhaps its my lack of understanding regarding the client, but yes, I am struggling with where exactly to put this configuration handling/setup.

My use case is like this:

I have a ViewModel that has the concept of an Authenticated Session (ISessionClient - a REST client to Java backend).
The ViewModel may make several asynchronous calls for various resources simultaneously.
The implementation of ISessionClient uses HttpClient to make REST requests.
The methods of the ISessionClient (such as IAsyncRequest Fetch<T>(Uri resourceUri, Action<T> resultHandler, ...) ) use a Task<T> internally to make a request against an HttpClient instance that has credentials, proxy, etc. set on an HttpClientHandler passed to the HttpClient when it is constructed.
Any or all request may be cancelled at any time via user interaction (cancel button). 

I was finding that initial requests were slow when I had to go through that config each time (particularly for proxy auto discovery).  So I was hoping to leverage a one-time config that I could use for the duration of the ViewModels lifetime (usually a short lived task with associated view).

A good example for this real world use case scenario would be awesome!

Thanks for your help!

Nov 1, 2011 at 2:36 PM

The HttpClient should have it's lifetime scoped to at least the viewmodel in my opinion.  The problem with disposing the HttpClient is that it disposes the HttpClientHandler that then closes the HTTP Connection.  It is re-opening that Http connection that is expensive.

I'm not a huge fan of the fact that we can pass in a HttpClientHandler into the HttpClient but then disposing the HttpClient disposes the handler. Seems counter-intuitive to me.  Hopefully the team will clarify their think around this design at some point.  I hear that some people are working on more documentation :-)

 

Nov 1, 2011 at 2:53 PM

Thanks Darrel,

My thought exactly and you confirmed my experience and hence my questioning.  I saw that the handler was disposed when the client was disposed...

It would be great to have handlers that could live between client instances.
In the case of HttpClientHandler (and derived types) keeping the handler alive would be hugely beneficial in many ways: think cookies, credentials, proxy, certification, and other custom configurations, etc.

Perhaps for my use case I can use an instance of HttpClient (and associated HttpClientHandler) until a request is cancelled then recreate the client again.  Not really what I'd like to do! 

Any suggestions on what other folk are doing for this case would be great!

Thanks

Coordinator
Nov 1, 2011 at 4:52 PM

This is good feedback – do you guys want to open an issue in the Issue Tracker so that the community can vote on it?

Thanks!

Daniel Roth

Nov 1, 2011 at 4:53 PM

Why can't you just keep the HttpClient alive and use the SendAsync method on HttpClient instead of creating your own Task to call a separate HTTPClient instance?

Coordinator
Nov 1, 2011 at 5:42 PM

By design handlers cannot be used across clients. The reasoning is that the innermost handler touches the wire i.e. it manages the actual network connection. The advantage as Darrel said is one the connection resources are created you don't pay a continual cost every time you send a connection. Remember also we use handlers on the server, on the server the outermost handler is hooked to the service endpoint and the innermost handler touches the wire.

Glenn

Nov 1, 2011 at 5:42 PM

Darrel, while the initial configuration of the HttpClient should be done up front in a thread safe way, it should be completely safe to call SendAsync from multiple thread at the same time.  If you find otherwise please let us know. 

Yes, HttpClient should also be safe to re-use after a cancelled request.

If the default Dispose pattern does not suit your needs, remember that these components are extensible so you can override Dispose on HttpClient or HttpClientHandler, or insert an intermediate handler between them to modify the behavior.

Nov 2, 2011 at 12:04 AM

Tratcher,

Thanks for confirming that.  It does make sense that SendAsync would be thread safe!

That does remind me though that there was an issue in earlier versions with HttpContent Headers not being thread-safe.  I just re-tested this with preview 5 and I can repro the issue with the following code:

 

		[Fact]
		public void ReadingContentTypeIsNotThreadSafe()
		{
			// Arrange
			// Create our own server/host so that the HttpResponseMessage for requests is
			// newed up the usual internal way from a raw stream
			var serviceUri = new Uri("http://localhost:1017/");
			var config = new HttpConfiguration();

			var host = new HttpServiceHost(typeof(FooService),config, new[] { serviceUri });
			host.Open();

			const int maxRuns = 50;

			var httpClient = new HttpClient();
		    httpClient.BaseAddress = serviceUri;

			var tasks = new List<Task>();
			var runs = 0;

			// Act
			do {
				var response = httpClient.Send(new HttpRequestMessage(HttpMethod.Get, "foo"));

				tasks.Clear();

				for (var j = 0; j < 5; ++j) {
					tasks.Add(Task.Factory.StartNew(() => {
						for (var k = 0; k < 100000; ++k) {
							var contentType = response.Content.Headers.ContentType;
						}
					}));
				}

				Task.WaitAll(tasks.ToArray());

				++runs;
				if (runs >= maxRuns) {
					break;
				}
			}
			while (tasks.All(t => t.Exception == null));

			host.Close();

			// Assert
			foreach (var task in tasks) {
				if (task.Exception != null) {
					Assert.False(false, task.Exception.Flatten().Message);
				}
			}
		}


		[ServiceContract]
		private class FooService
		{
			[WebGet(UriTemplate = "foo")]
			public HttpResponseMessage GetFoo()
			{
				var content = new StringContent("Blah blah blah");
				var response = new HttpResponseMessage { Content = content };

				return response;
			}
		}
	}

This test does not reproduce the error every time, but at least 1 out of 3 runs generates an InvalidCastException when reading the ContentType header.

I created an issue for this bug that I will point to this code sample.

Nov 2, 2011 at 7:53 AM
Edited Nov 2, 2011 at 8:07 AM

Hi,

Why don't I use SendAsync?
Because I need to do a few things before and after the call to Send and therefore it needs to be synchronous within the Task.  One thing in particular is that I need to handle an HttpStatusCode.Unauthorized responses (amongst others) which in this case will call out to a CredentialsProvider (which may be handled by a UI Logon service). I want to do this in that task so that it is handled in one place and also so that it will be triggered when the service times out the authorization due to a idle time (coffee break!).  So from the ViewModel I wand to call my ISessionClient.Fetch<T>(Uri resourceUri, Action<T> resultHandler, ...) and merrily let the underlying Task logic handle the re-authentications etc on a background thread.

Putting threading aside for one moment... here are some of the problems I'm encountering:

1) It appears that on the first call to to Send/SendAsync on HttpClient (with HttpClientHandler handler) the instance is locked down to prevent property changes to Credentials etc.  This is a problem because it forces me to dispose the current client and handler and all my config and recreated the HttpClient with new Credentials and try again - an expensive process.

2) Unconfirmed, BUT, the HttpClient creates an internal CancellationTokenSource (pendingRequestsCts) that is linked to my cancellation Token I pass in the call to Send on the HttpClient.  If my understanding is correct, once any linked Token is cancelled all linked Tokens are cancelled (a desired effect) AND the pendingRequestsCts flags that it is cancelled (also a desired effect).  However, it appears there is no way to 'uncancel' or reset this and once again it forces me to recreate the client.  Have I misunderstood how the CancellationTokenSource with linked CancellationTokens works? Does the CancellationTokenSource reset itself somehow or does the HttpClient create a new one on the next call to send or is the client rendered permanently 'cancelled'!?

[On further investigation I see in HttpClient.CancelPendingRequests a call to Interlocked.Exchange(ref pendingRequestsCts, new CancellationTokenSource()) - is this how I should 'reset' the cancelled state.  If so it would be great to have the locked down properties 'released' for modification at this point too!  Would this not provide a way for better re-use.]

Something smells about an attempt to re-use the HttpClient - I'm very open to suggestions on how I can maximise reuse of an already configured (and authenticated) HttpClient so as to minimise expensive reconfig scenarios (such as when there is an auth timeout).

Thank you for all the input on this subject.
(A little background on this is that our project is creating a WPF/Metro front end for a system used for medical purposes for a client base of approximately 3000 users - UI response times are critical and therefore UI interactions require efficient and mainly concurrent async loading of resources/data/tasks/etc)

Nov 2, 2011 at 4:29 PM

Darrel,

Interesting repro. Note that while HttpClient and handlers are designed for reuse and thread safety, messages and content are designed for single use and have the default .NET no-thread-safety-guarantee. This is a practical tradeoff to maintain acceptable performance in the API. If there are specific items that need thread safety, or that appear to be very thread fragile, let us know. The example above looks like it withstood quite a bit of abuse.

 

Nov 2, 2011 at 4:46 PM

Another,

1) Have you tried assigning a CredentialCache and then adding new credentials as you go?

2) That's not quite accurate. HttpClient takes pendingRequestsCts and your CTS and creates a third request CTS that has the timeout assigned to it and is then passed on with the request.  Canceling your CTS will cancel the request CTS, but not the pendingRequestsCts. Similarly the timeout will only affect the request CTS, not pendingRequestsCts or your CTS.

http://msdn.microsoft.com/en-us/library/dd642265.aspx

Are there any other scenarios that prevent you from reusing the existing HttpClient?

Nov 2, 2011 at 5:24 PM

Tratcher,

Thank you, the CredentialCache is what I need!

Today I have an implementation for reuse of the HttpClient in a multi-threaded use case and for now it seems to be holding up well.  I still need to fully test it.

I have one other scenario that the CredentialCache may be able to solve for me.  During the lifetime of an 'Authenticated Session' (a concept) the first time a request goes across the wire I will get a 401 which causes the Auth Session code to capture credentials and then reattempt the request (also another reason for my own task and not using the SendAsync).

Once the request is authenticated the server provides session id in the form of a cookie which is used to identify and authenticate a task session.  Once this id is available there is no further need to supply credentials for any calls on that client.  I guess here I could remove the associated credentials from the cache and continue with the session id.

 I think I'm good to go!  Will keep you posted!

Thanks!

Nov 3, 2011 at 2:06 PM

Tratcher,

I realize that the repro test I sent was a little extreme but we did first run into the issue in a realistic scenario.  In my RESTAgent (http://restagent.codeplex.com) project, when I receive a response I do two things.  One, I dispatch control to a response handler based on the content type and two I read and parse the payload looking for links.  This processing of the payload is done on a background thread because often the user-agent needs time to prep some kind of UI before it can display the data contained in the representation.  Sporadically our formatters would choke when they attempted to process the HTTP Content.

I suspect as HttpClient moves to an all async model, it may become more frequent for other people to do this kind of thing.

Darrel