Decidir entre HttpClient y WebClient [cerrado]
Nuestra aplicación web se ejecuta en .NET Framework 4.0. La interfaz de usuario llama a los métodos del controlador mediante llamadas Ajax.
Necesitamos consumir el servicio REST de nuestro proveedor. Estoy evaluando la mejor forma de llamar al servicio REST en .NET 4.0. El servicio REST requiere un esquema de autenticación básico y puede devolver datos tanto en XML como en JSON.
No hay ningún requisito para cargar/descargar datos enormes y no veo nada en el futuro. Eché un vistazo a algunos proyectos de código fuente abierto para consumo REST y no encontré ningún valor en ellos para justificar una dependencia adicional en el proyecto. Empecé a evaluar WebClient
y HttpClient
. Descargué HttpClient para .NET 4.0 de NuGet .
Busqué diferencias entre WebClient
y HttpClient
y este sitio mencionó que un solo HttpClient puede manejar llamadas simultáneas y puede reutilizar DNS resuelto, configuración de cookies y autenticación. Todavía tengo que ver los valores prácticos que podemos ganar debido a las diferencias.
Hice una prueba de rendimiento rápida para descubrir cómo funcionan WebClient
(llamadas síncronas), HttpClient
(síncronas y asincrónicas). Y aquí están los resultados:
Estoy usando la misma HttpClient
instancia para todas las solicitudes (mínimo - máximo).
Sincronización de WebClient: 8 ms - 167 ms
Sincronización de HttpClient: 3 ms - 7228 ms
HttpClient asíncrono: 985 - 10405 ms
Usando un nuevo HttpClient
para cada solicitud (mínimo - máximo):
Sincronización de WebClient: 4 ms - 297 ms
Sincronización de HttpClient: 3 ms - 7953 ms
HttpClient asíncrono: 1027 - 10834 ms
Código
public class AHNData
{
public int i;
public string str;
}
public class Program
{
public static HttpClient httpClient = new HttpClient();
private static readonly string _url = "http://localhost:9000/api/values/";
public static void Main(string[] args)
{
#region "Trace"
Trace.Listeners.Clear();
TextWriterTraceListener twtl = new TextWriterTraceListener(
"C:\\Temp\\REST_Test.txt");
twtl.Name = "TextLogger";
twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;
ConsoleTraceListener ctl = new ConsoleTraceListener(false);
ctl.TraceOutputOptions = TraceOptions.DateTime;
Trace.Listeners.Add(twtl);
Trace.Listeners.Add(ctl);
Trace.AutoFlush = true;
#endregion
int batchSize = 1000;
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = batchSize;
ServicePointManager.DefaultConnectionLimit = 1000000;
Parallel.For(0, batchSize, parallelOptions,
j =>
{
Stopwatch sw1 = Stopwatch.StartNew();
GetDataFromHttpClientAsync<List<AHNData>>(sw1);
});
Parallel.For(0, batchSize, parallelOptions,
j =>
{
Stopwatch sw1 = Stopwatch.StartNew();
GetDataFromHttpClientSync<List<AHNData>>(sw1);
});
Parallel.For(0, batchSize, parallelOptions,
j =>
{
using (WebClient client = new WebClient())
{
Stopwatch sw = Stopwatch.StartNew();
byte[] arr = client.DownloadData(_url);
sw.Stop();
Trace.WriteLine("WebClient Sync " + sw.ElapsedMilliseconds);
}
});
Console.Read();
}
public static T GetDataFromWebClient<T>()
{
using (var webClient = new WebClient())
{
webClient.BaseAddress = _url;
return JsonConvert.DeserializeObject<T>(
webClient.DownloadString(_url));
}
}
public static void GetDataFromHttpClientSync<T>(Stopwatch sw)
{
HttpClient httpClient = new HttpClient();
var response = httpClient.GetAsync(_url).Result;
var obj = JsonConvert.DeserializeObject<T>(
response.Content.ReadAsStringAsync().Result);
sw.Stop();
Trace.WriteLine("HttpClient Sync " + sw.ElapsedMilliseconds);
}
public static void GetDataFromHttpClientAsync<T>(Stopwatch sw)
{
HttpClient httpClient = new HttpClient();
var response = httpClient.GetAsync(_url).ContinueWith(
(a) => {
JsonConvert.DeserializeObject<T>(
a.Result.Content.ReadAsStringAsync().Result);
sw.Stop();
Trace.WriteLine("HttpClient Async " + sw.ElapsedMilliseconds);
}, TaskContinuationOptions.None);
}
}
}
Mis preguntas
- Las llamadas REST regresan en 3-4 segundos, lo cual es aceptable. Las llamadas al servicio REST se inician en los métodos del controlador que se invocan desde las llamadas Ajax. Para empezar, las llamadas se ejecutan en un hilo diferente y no bloquean la interfaz de usuario. Entonces, ¿puedo seguir con las llamadas sincrónicas?
- El código anterior se ejecutó en mi localbox. En una configuración de producción, estarán involucradas las búsquedas de DNS y proxy. ¿ Existe alguna ventaja de usar
HttpClient
overWebClient
? - ¿ Es
HttpClient
la concurrencia mejor queWebClient
? Según los resultados de las pruebas, veo queWebClient
las llamadas sincrónicas funcionan mejor. - ¿ Será
HttpClient
una mejor opción de diseño si actualizamos a .NET 4.5? El rendimiento es el factor clave del diseño.
HttpClient es la API más nueva y tiene los beneficios de
- tiene un buen modelo de programación asincrónica
- Henrik F Nielson, que es básicamente uno de los inventores de HTTP, está trabajando en él y diseñó la API para que le resulte fácil seguir el estándar HTTP, por ejemplo, generando encabezados compatibles con los estándares.
- está en .NET framework 4.5, por lo que tiene cierto nivel garantizado de soporte para el futuro previsible
- También tiene la versión xcopy able/portable-framework de la biblioteca si desea usarla en otras plataformas: .NET 4.0, Windows Phone , etc.
Si está escribiendo un servicio web que realiza llamadas REST a otros servicios web, debería utilizar un modelo de programación asincrónica para todas sus llamadas REST, de modo que no se quede sin subprocesos. Probablemente también quieras utilizar el compilador de C# más nuevo que tiene soporte async/await .
Nota: No tiene más rendimiento, AFAIK. Probablemente tenga un rendimiento similar si crea una prueba justa.
HttpClientFactory
Es importante evaluar las diferentes formas en que se puede crear un HttpClient y parte de eso es comprender HttpClientFactory.
https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
Esta no es una respuesta directa, lo sé, pero es mejor comenzar aquí que terminar en new HttpClient(...)
todas partes.
Cuando se trata de aplicaciones ASP.NET, todavía las prefiero WebClient
porque HttpClient
:
- La implementación moderna viene con métodos asincrónicos/en espera basados en tareas.
- Tiene una huella de memoria más pequeña y es de 2 a 5 veces más rápido (otras respuestas ya lo mencionan)
- Se sugiere " reutilizar una única instancia de HttpClient durante toda la vida útil de su aplicación ". Pero ASP.NET no tiene una "vida útil de la aplicación", solo una vida útil de una solicitud. La guía actual para ASP.NET 5 es usar
HttpClientFactory
, pero solo se puede usar mediante inyección de dependencia. Algunas personas quieren una solución más sencilla. - Lo más importante es que si está utilizando una instancia única de HttpClient durante la vida útil de la aplicación, como sugiere MS, tiene problemas conocidos . Por ejemplo, el problema del almacenamiento en caché de DNS: HttpClient simplemente ignora el TTL y almacena en caché el DNS "para siempre". Sin embargo, existen soluciones. Si desea obtener más información sobre los problemas y la confusión con HttpClient, simplemente lea este comentario en Microsoft GitHub.