Bagaimana cara menggunakan HttpWebRequest (.NET) secara tidak sinkron?

156

Bagaimana saya bisa menggunakan HttpWebRequest (.NET, C #) secara tidak sinkron?

Jason
sumber
1
Lihat artikel ini di Developer Fusion: developerfusion.com/code/4654/asynchronous-httpwebrequest
Anda juga dapat melihat yang berikut, untuk contoh yang cukup lengkap untuk melakukan apa yang diminta Jason: stuff.seans.com/2009/01/05/… Sean
Sean Sexton
1
gunakan async msdn.microsoft.com/en-us/library/…
Raj Kaimal
1
sejenak, saya bertanya-tanya apakah Anda mencoba mengomentari utas rekursif?
Kyle Hodgson

Jawaban:

125

Menggunakan HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

Fungsi panggilan balik dipanggil saat operasi asinkron selesai. Anda harus setidaknya menelepon EndGetResponse()dari fungsi ini.

Jon B
sumber
16
BeginGetResponse tidak begitu berguna untuk penggunaan async. Tampaknya memblokir ketika mencoba menghubungi sumber. Coba cabut kabel jaringan Anda atau berikan uri yang salah bentuk, lalu jalankan kode ini. Sebaliknya, Anda mungkin perlu menjalankan GetResponse pada utas kedua yang Anda berikan.
Ash
2
@AshleyHenderson - Bisakah Anda memberi saya contoh?
Tohid
1
@ Tohid di sini adalah kelas penuh dengan sampel yang saya gunakan dengan Unity3D.
cregox
3
Anda harus menambahkan webRequest.Proxy = nulluntuk mempercepat permintaan secara dramatis.
Trontor
C # melempar kesalahan dengan memberi tahu saya bahwa ini adalah kelas yang usang
AleX_
67

Mempertimbangkan jawabannya:

HttpWebRequest webRequest;

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

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

Anda dapat mengirim pointer permintaan atau objek lain seperti ini:

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

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

Salam pembuka

xlarsx
sumber
7
Memberi +1 untuk opsi yang tidak melampaui variabel 'permintaan', tetapi Anda bisa membuat pemeran alih-alih menggunakan kata kunci "sebagai". InvalidCastException akan dibuang alih-alih NullReferenceException yang membingungkan
Davi Fiamenghi
64

Semua orang sejauh ini salah, karena BeginGetResponse()beberapa bekerja pada utas saat ini. Dari dokumentasi :

Metode BeginGetResponse memerlukan beberapa tugas pengaturan yang sinkron untuk diselesaikan (resolusi DNS, deteksi proxy, dan koneksi soket TCP, misalnya) sebelum metode ini menjadi tidak sinkron. Akibatnya, metode ini tidak boleh dipanggil pada utas antarmuka pengguna (UI) karena mungkin butuh waktu yang cukup lama (hingga beberapa menit tergantung pada pengaturan jaringan) untuk menyelesaikan tugas pengaturan sinkron awal sebelum pengecualian untuk kesalahan dilemparkan atau metode ini berhasil.

Jadi untuk melakukan ini dengan benar:

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);
}

Anda kemudian dapat melakukan apa yang perlu dilakukan dengan respons. Sebagai contoh:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});
Isak
sumber
2
Tidak bisakah Anda memanggil metode GetResponseAsync dari HttpWebRequest menggunakan menunggu (dengan asumsi Anda membuat fungsi Anda sebagai sinkronisasi)? Saya sangat baru di C # jadi ini mungkin omong kosong ...
Brad
GetResponseAsync terlihat bagus, meskipun Anda memerlukan .NET 4.5 (saat ini beta).
Isak
15
Yesus. Itu adalah beberapa kode jelek. Mengapa kode async tidak dapat dibaca?
John Shedletsky
Mengapa Anda perlu request.BeginGetResponse ()? Mengapa wrapperAction.BeginInvoke () tidak cukup?
Igor Gatis
2
@Gatis Ada dua tingkat panggilan asinkron - wrapperAction.BeginInvoke () adalah panggilan asinkron pertama ke ekspresi lambda yang memanggil permintaan.BeginGetResponse (), yang merupakan panggilan asinkron kedua. Seperti yang Isak tunjukkan, BeginGetResponse () memerlukan beberapa pengaturan sinkron, itulah sebabnya ia membungkusnya dalam panggilan asinkron tambahan.
walkingTarget
64

Sejauh ini cara termudah adalah dengan menggunakan TaskFactory.FromAsync dari TPL . Ini benar-benar beberapa baris kode ketika digunakan bersama dengan kata kunci async / menunggu baru :

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);

Jika Anda tidak dapat menggunakan kompiler C # 5 maka hal di atas dapat dilakukan dengan menggunakan metode Task.ContinueWith :

Task.Factory.FromAsync<WebResponse>(request.BeginGetResponse,
                                    request.EndGetResponse,
                                    null)
    .ContinueWith(task =>
    {
        var response = (HttpWebResponse) task.Result;
        Debug.Assert(response.StatusCode == HttpStatusCode.OK);
    });
Nathan Baulch
sumber
Sejak .NET 4 pendekatan TAP ini lebih disukai. Lihat contoh serupa dari MS - "Cara: Bungkus Pola EAP dalam Tugas" ( msdn.microsoft.com/en-us/library/ee622454.aspx )
Alex Klaus
Jauh lebih mudah daripada cara lain
Don Rolling
8

Saya akhirnya menggunakan BackgroundWorker, itu pasti asinkron tidak seperti beberapa solusi di atas, ia menangani kembali ke utas GUI untuk Anda, dan sangat mudah dimengerti.

Ini juga sangat mudah untuk menangani pengecualian, karena mereka berakhir di metode RunWorkerCompleted, tetapi pastikan Anda membaca ini: Pengecualian yang tidak ditangani di BackgroundWorker

Saya menggunakan WebClient tetapi jelas Anda bisa menggunakan HttpWebRequest.GetResponse jika Anda mau.

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();
Eggbert
sumber
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);
}
dragansr
sumber
6

.NET telah berubah sejak banyak jawaban ini diposkan, dan saya ingin memberikan jawaban yang lebih mutakhir. Gunakan metode async untuk memulai Taskyang akan berjalan di utas latar:

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;
}

Untuk menggunakan metode async:

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

Memperbarui:

Solusi ini tidak berfungsi untuk aplikasi UWP yang menggunakan WebRequest.GetResponseAsync()alih-alih WebRequest.GetResponse(), dan tidak memanggil Dispose()metode yang sesuai. @dragansr memiliki solusi alternatif yang baik yang mengatasi masalah ini.

tukang trem
sumber
1
Terima kasih ! Telah mencoba menemukan contoh async, banyak contoh menggunakan pendekatan lama yang terlalu kompleks.
WDUK
Apakah ini tidak akan memblokir utas untuk setiap respons? tampaknya agak berbeda dengan mis. docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Pete Kirkham
@PeteKirkham Utas latar belakang sedang melakukan permintaan, bukan utas UI. Tujuannya adalah untuk menghindari memblokir utas UI. Metode apa pun yang Anda pilih untuk membuat permintaan akan memblokir utas membuat permintaan. Contoh Microsoft yang Anda maksud adalah mencoba membuat beberapa permintaan, tetapi mereka masih membuat Tugas (utas latar) untuk permintaan tersebut.
tronman
3
Agar jelas, ini adalah kode sinkron / pemblokiran 100%. Untuk menggunakan async, WebRequest.GetResponseAsync()dan StreamReader.ReadToEndAync()perlu digunakan dan ditunggu.
Richard Szalay
4
@tronman Menjalankan metode pemblokiran dalam Tugas saat async ekuivalen tersedia adalah anti-pola yang sangat tidak disarankan. Meskipun tidak membatalkan utas panggilan, ia tidak melakukan apa pun untuk skala untuk skenario web hosting karena Anda hanya memindahkan pekerjaan ke utas lain daripada menggunakan port penyelesaian IO untuk mencapai asinkron.
Richard Szalay
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);
        } 
    }
Sten Petrov
sumber