Po co używać HttpClient do połączenia synchronicznego

188

Buduję bibliotekę klas do interakcji z interfejsem API. Muszę wywołać interfejs API i przetworzyć odpowiedź XML. Widzę zalety korzystania HttpClientz łączności asynchronicznej, ale to, co robię, jest czysto synchroniczne, więc nie widzę żadnej znaczącej korzyści z używania HttpWebRequest.

Jeśli ktoś mógłby rzucić jakiekolwiek światło, byłbym bardzo wdzięczny. Nie jestem zwolennikiem korzystania z nowych technologii ze względu na to.

Keczup
źródło
3
Nienawidzę ci mówić, ale połączenie przez HTTP nigdy nie jest czysto synchroniczne z powodu tego, jak wewnętrznie działa sieć Windows (inaczej porty uzupełniające).
TomTom

Odpowiedzi:

374

ale to, co robię, jest czysto synchroniczne

Możesz użyć HttpClientdo synchronicznych żądań w porządku:

using (var client = new HttpClient())
{
    var response = client.GetAsync("http://google.com").Result;

    if (response.IsSuccessStatusCode)
    {
        var responseContent = response.Content; 

        // by calling .Result you are synchronously reading the result
        string responseString = responseContent.ReadAsStringAsync().Result;

        Console.WriteLine(responseString);
    }
}

Jeśli chodzi dlaczego należy użyć HttpClientw ciągu WebRequestobawia się, dobrze, HttpClientjest New Kids on the Block i może zawierać ulepszenia w stosunku do starego klienta.

Darin Dimitrov
źródło
27
Czy synchroniczne użycie metod asynchronicznych potencjalnie nie zablokuje wątku interfejsu użytkownika? Może warto rozważyć coś takiego string responseString = Task.Run(() => responseContent.ReadAsStringAsync()).Result;zamiast jeśli musi uczynić to synchroniczny.
Ziemia
13
@earthling, tak, Task.Runwywołuje zadanie z ThreadPool, ale wywołujesz .Resultgo, zabijając wszystkie korzyści z tego i blokując wątek, w którym to wywołałeś .Result(co zwykle jest głównym wątkiem interfejsu użytkownika).
Darin Dimitrov
35
Zgodnie z tym postem ( blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx ) dzwonienie w .Resultten sposób może wyczerpać pulę wątków i spowodować impas.
Pete Garafano
16
Ten kod zawsze będzie zakleszczony, jeśli zostanie wykonany w ramach zadania utworzonego w głównym wątku interfejsu użytkownika przeznew TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()
Wima Coenena
24
Jak więc używać HttpClient synchronicznie z wątku interfejsu użytkownika? Powiedzmy, że celowo chcę zablokować wątek interfejsu użytkownika (lub piszę aplikację konsolową), dopóki nie otrzymam odpowiedzi HTTP ... Tak więc, jeśli Wait (), Result () itp. Może powodować zakleszczenia, jakie jest ostateczne rozwiązanie tego problemu bez ryzyko impasu i bez korzystania z innych klas, takich jak WebClient?
Dexter,
26

Powtórzę ponownie odpowiedź Donny V. i Josha

„Jedynym powodem, dla którego nie używałbym wersji asynchronicznej jest to, że próbuję obsługiwać starszą wersję platformy .NET, która nie ma jeszcze wbudowanej obsługi asynchronicznej.”

(i głosuj, gdybym miał reputację).

Nie pamiętam, kiedy ostatni raz, jeśli kiedykolwiek, byłem wdzięczny za to, że HttpWebRequest rzucił wyjątki dla kodów statusu> = 400. Aby obejść te problemy, musisz natychmiast wychwycić wyjątki i odwzorować je na niektóre mechanizmy odpowiedzi inne niż wyjątki w twoim kodzie ... sam w sobie nudny, nużący i podatny na błędy. Niezależnie od tego, czy komunikujesz się z bazą danych, czy wdrażasz specjalnie skonfigurowany serwer proxy sieci Web, „prawie” zawsze jest pożądane, aby sterownik Http po prostu powiedział kodowi aplikacji, co zostało zwrócone, i pozostawienie decyzji o tym, jak się zachować.

Dlatego preferowany jest HttpClient.

trev
źródło
1
Byłem zaskoczony, że HttpClientsama jest otoczką HttpWebRequest(która rzeczywiście łapie te WebExceptionobiekty i dokonuje konwersji HttpResponseMessagena Ciebie). Myślałem, że łatwiej będzie zbudować nowego klienta całkowicie od zera.
Dai,
4
Istnieje wiele dobrych powodów, dla których nie chcesz przepisywać całej bazy kodu tylko dla bardzo niskiego poziomu wywołania HTTP, które nie jest nawet krytyczne pod względem wydajności (ale wprowadziłoby asynchronię w milionach miejsc).
FrankyBoy
W .net core 2, jeśli chcesz dynamicznie oceniać wyrażenie za pomocą DynamicExpressionParser, użycie asynchronii może być niemożliwe; indeksujący nieruchomości nie mogą używać asynchronizacji; w mojej sytuacji muszę dynamicznie oceniać ciąg taki jak „GetDefaultWelcomeMessage [\„ InitialMessage \ ”]”, gdzie ta metoda tworzy HttpCall, a składnia indeksu jest lepsza niż składnia metody „Util.GetDefaultWelcomeMessage (\„ InitialMessage \ ”)”
eugen
7

Jeśli budujesz bibliotekę klas, być może użytkownicy Twojej biblioteki chcieliby korzystać z niej asynchronicznie. Myślę, że to największy powód.

Nie wiesz także, w jaki sposób twoja biblioteka będzie używana. Być może użytkownicy będą przetwarzać wiele żądań, a robienie tego asynchronicznie pomoże mu działać szybciej i wydajniej.

Jeśli możesz to zrobić po prostu, staraj się nie obciążać użytkowników biblioteki, próbując uczynić przepływ asynchronicznym, kiedy możesz się nim zająć.

Jedynym powodem, dla którego nie używałbym wersji asynchronicznej jest to, że próbuję obsługiwać starszą wersję platformy .NET, która nie ma jeszcze wbudowanej obsługi asynchronicznej.

Josh Smeaton
źródło
Rozumiem, więc uczyń bibliotekę klas asynchroniczną i pozwól użytkownikom systemu zdecydować, czy użyć jej asynchronicznie, czy też używać funkcji oczekiwania i używania synchronicznego?
Ketchup
erm, czekaj pomaga asynchronicznie wykonywać niektóre połączenia, zwracając kontrolę nad dzwoniącym.
Josh Smeaton
6

W moim przypadku zaakceptowana odpowiedź nie zadziałała. Dzwoniłem do API z aplikacji MVC, która nie miała akcji asynchronicznych.

Oto jak udało mi się sprawić, aby działało:

private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
public static T RunSync<T>(Func<Task<T>> func)
    {           
        CultureInfo cultureUi = CultureInfo.CurrentUICulture;
        CultureInfo culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew<Task<T>>(delegate
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap<T>().GetAwaiter().GetResult();
    }

Potem nazwałem to tak:

Helper.RunSync(new Func<Task<ReturnTypeGoesHere>>(async () => await AsyncCallGoesHere(myparameter)));
Jonathan Alfaro
źródło
1
Dzięki @Darkonekt ... To działa idealnie dla mnie. Tylko HttpClient.SendAsync (...). Wynik nigdy nie działa w programie obsługi AspNet (.ASHX).
Rafael Kazuo Sato Simiao
3
public static class AsyncHelper  
{
    private static readonly TaskFactory _taskFactory = new
        TaskFactory(CancellationToken.None,
                    TaskCreationOptions.None,
                    TaskContinuationOptions.None,
                    TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        => _taskFactory
            .StartNew(func)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    public static void RunSync(Func<Task> func)
        => _taskFactory
            .StartNew(func)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

Następnie

AsyncHelper.RunSync(() => DoAsyncStuff());

jeśli użyjesz tej klasy jako parametru, przekażesz metodę asynchroniczną, możesz wywoływać metody asynchroniczne z metod synchronizacji w bezpieczny sposób.

wyjaśniono to tutaj: https://cpratt.co/async-tips-tricks/

Lean Bonaventura
źródło
-1

Wydaje się, że wszystkie odpowiedzi koncentrują się na użyciu HttpClientsynchronicznym zamiast na udzielaniu rzeczywistej odpowiedzi na pytanie.

HttpClientjest czymś więcej niż prostym modułem obsługi zapytań / odpowiedzi, więc może poradzić sobie z pewnymi osobliwościami różnych sieci. Mianowicie w moim przypadku praca z serwerem proxy NTLM, który wymaga negocjacji, wysyłanie wielu żądań / odpowiedzi z tokenami i poświadczeniami między klientem a serwerem proxy w celu uwierzytelnienia. HttpClient(using HttpClientHandler) wydaje się mieć wbudowany mechanizm, który obsługuje jedno kliknięcie metody, które zwraca zasoby poza proxy.

Bizniztime
źródło
Twoja odpowiedź nie wyjaśnia również, jak używać HttpClient asynchronicznie.
user275801
@ user275801 To jest głupi komentarz. Nikt o to nie pytał. Domyślnie jest asynchroniczny.
Bizniztime