Як користуватися HttpWebRequest (.NET) асинхронно?


156

Як я можу використовувати HttpWebRequest (.NET, C #) асинхронно?


1
Перегляньте цю статтю про Fusion Developer: developerfusion.com/code/4654/asynchronous-httpwebrequest

Ви також можете побачити наступне, для досить повного прикладу виконання того, що просить Джейсон: stuff.seans.com/2009/01/05/… Шон
Шон Секстон

1
використовувати async msdn.microsoft.com/en-us/library/…
Радж Каймал

1
на мить я задумався, чи намагаєтесь ви прокоментувати рекурсивну нитку?
Кайл Ходжсон

Відповіді:


125

Використовуйте HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

void StartWebRequest()
{
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}

void FinishWebRequest(IAsyncResult result)
{
    webRequest.EndGetResponse(result);
}

Функція зворотного дзвінка викликається, коли асинхронна операція завершена. Вам потрібно принаймні зателефонувати EndGetResponse()з цієї функції.


16
BeginGetResponse не так корисний для використання асинхронізації. Здається, блокується при спробі зв’язатися з ресурсом. Спробуйте відключити мережевий кабель або дати йому неправильну форму, а потім запустіть цей код. Натомість вам, ймовірно, потрібно запустити GetResponse на другому наданому вами потоці.
Еш

2
@AshleyHenderson - Не могли б ви надати мені зразок?
Тохід

1
@Tohid тут повний клас із зразком, який я використовував з Unity3D.
Крегокс

3
Ви повинні додати, webRequest.Proxy = nullщоб швидко пришвидшити запит.
Тронтор

C # кидає помилку, повідомляючи мені, що це застарілий клас
AleX_

67

Враховуючи відповідь:

HttpWebRequest webRequest;

void StartWebRequest()
{
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
}

void FinishWebRequest(IAsyncResult result)
{
    webRequest.EndGetResponse(result);
}

Ви можете надіслати покажчик запиту або будь-який інший подібний об’єкт:

void StartWebRequest()
{
    HttpWebRequest webRequest = ...;
    webRequest.BeginGetResponse(new AsyncCallback(FinishWebRequest), webRequest);
}

void FinishWebRequest(IAsyncResult result)
{
    HttpWebResponse response = (result.AsyncState as HttpWebRequest).EndGetResponse(result) as HttpWebResponse;
}

Привітання


7
+1 для параметра, який не перевищує масштаб змінної "запит", але ви могли зробити команду замість використання ключового слова "як". InvalidCastException буде кинуто замість заплутаного NullReferenceException
Davi Fiamenghi

64

Усі поки що помилялися, бо BeginGetResponse()якась робота над поточною ниткою. З документації :

Метод BeginGetResponse вимагає завершення деяких завдань синхронної настройки (роздільна здатність DNS, виявлення проксі і підключення TCP-сокета), перш ніж цей метод стане асинхронним. Як результат, цей спосіб ніколи не повинен викликатись у потоці користувальницького інтерфейсу (UI), оскільки це може зайняти значний час (до декількох хвилин, залежно від мережевих налаштувань), щоб виконати початкові завдання синхронного налаштування перед тим, як буде викинуто виняток для помилки або метод вдається.

Отже, щоб зробити це правильно:

void DoWithResponse(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
    Action wrapperAction = () =>
    {
        request.BeginGetResponse(new AsyncCallback((iar) =>
        {
            var response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
            responseAction(response);
        }), request);
    };
    wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
    {
        var action = (Action)iar.AsyncState;
        action.EndInvoke(iar);
    }), wrapperAction);
}

Потім ви можете зробити все, що вам потрібно, з відповіддю. Наприклад:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});

2
Не могли б ви просто зателефонувати методу GetResponseAsync HttpWebRequest, використовуючи функцію await (якщо припустити, що ви виконали функцію асинхронізації)? Я дуже новачок у C #, тому це може бути повним дзижчанням ...
Бред

GetResponseAsync виглядає добре, хоча вам знадобиться .NET 4.5 (зараз бета-версія).
Ісак

15
Ісус. Це якийсь потворний код. Чому не можна асинхронний код читати?
Іван Шедлецький

Для чого потрібен запит.BeginGetResponse ()? Чому wrapperAction.BeginInvoke () не вистачає?
Ігор Гатіс

2
@Gatis Є два рівні асинхронних викликів - wrapperAction.BeginInvoke () - це перший асинхронний виклик лямбда-виразу, який викликає request.BeginGetResponse (), який є другим асинхронним викликом. Як зазначає Ісак, BeginGetResponse () вимагає певного синхронного налаштування, саме тому він завершує його в додатковий асинхронний виклик.
walkTarget

64

На сьогодні найпростіший спосіб - це використовувати TaskFactory.FromAsync з TPL . Це буквально пара рядків коду при використанні разом із новими ключовими словами асинхрон / очікування :

var request = WebRequest.Create("http://www.stackoverflow.com");
var response = (HttpWebResponse) await Task.Factory
    .FromAsync<WebResponse>(request.BeginGetResponse,
                            request.EndGetResponse,
                            null);
Debug.Assert(response.StatusCode == HttpStatusCode.OK);

Якщо ви не можете використовувати компілятор C # 5, то вищезазначене може бути виконано за допомогою методу Task.ContinueWith :

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });

Оскільки .NET 4, такий підхід TAP є кращим. Дивіться подібний приклад з MS - "Як: загортати шаблони EAP у завдання" ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Алекс Клаус

Шлях легший за інші способи
Дон

8

Я в кінцевому підсумку використовував BackgroundWorker, це, безумовно, асинхронно на відміну від деяких з вищезазначених рішень, він обробляє повернення до потоку GUI для вас, і це дуже легко зрозуміти.

З винятками також дуже легко обробляти винятки, оскільки вони закінчуються методом RunWorkerCompleted, але переконайтеся, що ви прочитали це: Неопрацьовані винятки в BackgroundWorker

Я використовував WebClient, але, очевидно, ви можете використовувати HttpWebRequest.GetResponse, якщо хочете.

var worker = new BackgroundWorker();

worker.DoWork += (sender, args) => {
    args.Result = new WebClient().DownloadString(settings.test_url);
};

worker.RunWorkerCompleted += (sender, e) => {
    if (e.Error != null) {
        connectivityLabel.Text = "Error: " + e.Error.Message;
    } else {
        connectivityLabel.Text = "Connectivity OK";
        Log.d("result:" + e.Result);
    }
};

connectivityLabel.Text = "Testing Connectivity";
worker.RunWorkerAsync();

7
public static async Task<byte[]> GetBytesAsync(string url) {
    var request = (HttpWebRequest)WebRequest.Create(url);
    using (var response = await request.GetResponseAsync())
    using (var content = new MemoryStream())
    using (var responseStream = response.GetResponseStream()) {
        await responseStream.CopyToAsync(content);
        return content.ToArray();
    }
}

public static async Task<string> GetStringAsync(string url) {
    var bytes = await GetBytesAsync(url);
    return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}

6

.NET змінився з моменту опублікування багатьох цих відповідей, і я хотів би надати більш сучасну відповідь. Використовуйте метод асинхронізації, щоб почати Taskкоманду, яка буде працювати на фоновому потоці:

private async Task<String> MakeRequestAsync(String url)
{    
    String responseText = await Task.Run(() =>
    {
        try
        {
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            WebResponse response = request.GetResponse();            
            Stream responseStream = response.GetResponseStream();
            return new StreamReader(responseStream).ReadToEnd();            
        }
        catch (Exception e)
        {
            Console.WriteLine("Error: " + e.Message);
        }
        return null;
    });

    return responseText;
}

Щоб використовувати метод асинхронізації:

String response = await MakeRequestAsync("http://example.com/");

Оновлення:

Це рішення не працює для програм UWP, які використовують WebRequest.GetResponseAsync()замість них WebRequest.GetResponse(), і не називає Dispose()методи, де це доречно. @dragansr має гарне альтернативне рішення, яке вирішує ці проблеми.


1
Дякую ! Намагалися знайти приклад асинхронізації, безліч прикладів із використанням старого підходу, який є надскладним.
WDUK

Чи це не блокуватиме потік для кожної відповіді? це здається зовсім іншим, наприклад, docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Піт Кіркхем,

@PeteKirkham Фоновий потік виконує запит, а не потік інтерфейсу. Мета - уникнути блокування потоку інтерфейсу користувача. Будь-який метод, який ви вирішите зробити із запиту, заблокує потік, що робить запит. Приклад Microsoft, на який ви посилаєтеся, намагається зробити кілька запитів, але вони все ще створюють завдання (фонова нитка) для запитів.
tronman

3
Щоб було зрозуміло, це 100% синхронний / блокуючий код. Для використання асінхр, WebRequest.GetResponseAsync()і StreamReader.ReadToEndAync()потрібно використовувати і чекали.
Річард Шалай

4
@tronman Запуск методів блокування у завданні, коли доступні еквіваленти асинхронізування, є сильно обережним анти-шаблоном. Хоча він і розблокує викликовий потік, він не робить нічого для масштабування сценаріїв веб-хостингу, оскільки ви просто переміщаєте роботу до іншого потоку, а не використовуєте порти завершення вводу-виводу для досягнення асинхронії.
Річард Шалай

3
public void GetResponseAsync (HttpWebRequest request, Action<HttpWebResponse> gotResponse)
    {
        if (request != null) { 
            request.BeginGetRequestStream ((r) => {
                try { // there's a try/catch here because execution path is different from invokation one, exception here may cause a crash
                    HttpWebResponse response = request.EndGetResponse (r);
                    if (gotResponse != null) 
                        gotResponse (response);
                } catch (Exception x) {
                    Console.WriteLine ("Unable to get response for '" + request.RequestUri + "' Err: " + x);
                }
            }, null);
        } 
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.