Nasza aplikacja internetowa działa w .Net Framework 4.0. Interfejs użytkownika wywołuje metody kontrolera za pośrednictwem wywołań ajax.
Musimy skorzystać z usługi REST od naszego dostawcy. Oceniam najlepszy sposób na wywołanie usługi REST w .Net 4.0. Usługa REST wymaga podstawowego schematu uwierzytelniania i może zwracać dane zarówno w formacie XML, jak i JSON. Nie ma wymogu przesyłania / pobierania dużych danych i nie widzę niczego w przyszłości. Przyjrzałem się kilku projektom z otwartym kodem źródłowym dla zużycia REST i nie znalazłem w nich żadnej wartości uzasadniającej dodatkową zależność w projekcie. Zaczął oceniać WebClient
i HttpClient
. Pobrałem HttpClient dla .Net 4.0 z NuGet.
Szukałem różnic między WebClient
a HttpClient
i ta strona wspomniano, że pojedyncze HttpClient mogą obsługiwać połączeń równoczesnych i może ponownego wykorzystania rozwiązany DNS, cookie config i uwierzytelniania. Mam jeszcze do czynienia z praktycznymi wartościami, które możemy zyskać z powodu różnic.
Zrobiłem szybki test wydajności, aby dowiedzieć się, jak działają WebClient
(synchronizacja połączeń), HttpClient
(synchronizacja i asynchronizacja). a oto wyniki:
Korzystanie z tej samej HttpClient
instancji dla wszystkich żądań (min. - maks.)
Synchronizacja WebClient: 8 ms - 167 ms
HttpClient synchronizacja: 3 ms - 7228 ms
HttpClient asynchronizacja: 985 - 10405 ms
Używanie nowego HttpClient
dla każdego żądania (min. - maks.)
Synchronizacja WebClient: 4 ms - 297 ms
HttpClient synchronizacja: 3 ms - 7953 ms
HttpClient asynchroniczna: 1027 - 10834 ms
Kod
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);
}
}
}
Moje pytania
- Połączenia REST zwracają w ciągu 3-4 sekund, co jest dopuszczalne. Wywołania usługi REST są inicjowane metodami kontrolera, które są wywoływane z wywołań ajax. Na początek wywołania działają w innym wątku i nie blokują interfejsu użytkownika. Czy mogę po prostu trzymać się synchronizacji połączeń?
- Powyższy kod został uruchomiony w mojej lokalnej skrzynce. W konfiguracji prod będą zaangażowane wyszukiwanie DNS i proxy. Czy jest jakaś korzyść z używania
HttpClient
ponadWebClient
? - Czy
HttpClient
współbieżność jest lepsza niżWebClient
? Z wyników testu widzę, żeWebClient
połączenia synchroniczne działają lepiej. - Czy
HttpClient
będzie lepszy wybór projektu, jeśli uaktualnimy do .Net 4.5? Wydajność jest kluczowym czynnikiem konstrukcyjnym.
GetDataFromHttpClientAsync
ponieważ uruchamia się jako pierwszy, inne wywołania mogą skorzystać z potencjalnego przetwarzania danych (na komputerze lokalnym lub dowolnym przezroczystym serwerze proxy między tobą a miejscem docelowym) i będą szybsze. Również w odpowiednich warunkachvar response = httpClient.GetAsync("http://localhost:9000/api/values/").Result;
może dojść do impasu z powodu wyczerpania wątków puli wątków. Nigdy nie powinieneś blokować działania zależnego od puli wątków w wątkach ThreadPool, powinieneśawait
zamiast tego zwrócić wątek z powrotem do puli.