วิธีใช้ HttpWebRequest (.NET) แบบอะซิงโครนัส


156

ฉันจะใช้ HttpWebRequest (.NET, C #) แบบอะซิงโครนัสได้อย่างไร


1
ลองอ่านบทความเกี่ยวกับ Developer Fusion: developerfusion.com/code/4654/asynchronous-httpwebrequest

คุณยังสามารถดูสิ่งต่อไปนี้สำหรับตัวอย่างที่สมบูรณ์แบบในการทำสิ่งที่เจสันถาม: stuff.seans.com/2009/01/05/… Sean
Sean Sexton


1
ครู่หนึ่งฉันสงสัยว่าคุณกำลังพยายามที่จะแสดงความคิดเห็นในกระทู้ซ้ำ?
Kyle Hodgson

คำตอบ:


125

ใช้ HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

ฟังก์ชันการเรียกกลับถูกเรียกเมื่อการดำเนินการแบบอะซิงโครนัสเสร็จสมบูรณ์ อย่างน้อยคุณต้องโทรEndGetResponse()จากฟังก์ชั่นนี้


16
BeginGetResponse ไม่มีประโยชน์สำหรับการใช้ async ดูเหมือนว่าจะบล็อกในขณะที่พยายามติดต่อทรัพยากร ลองถอดสายเคเบิลเครือข่ายของคุณหรือระบุว่า uri ผิดปกติจากนั้นเรียกใช้รหัสนี้ แต่คุณอาจต้องใช้ GetResponse แทนในเธรดที่สองที่คุณให้
Ash

2
@AshleyHenderson - คุณช่วยให้ฉันตัวอย่างได้ไหม?
Tohid


3
คุณควรเพิ่มwebRequest.Proxy = nullความเร็วในการร้องขออย่างมาก
Trontor

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 โดยใช้การรอ (สมมติว่าคุณทำฟังก์ชั่น async) หรือไม่? ฉันใหม่มากใน C # ดังนั้นนี่อาจเป็นคำพูดเหลวไหลที่สมบูรณ์ ...
แบรด

GetResponseAsync ดูดีแม้ว่าคุณจะต้องใช้. NET 4.5 (รุ่นเบต้าปัจจุบัน)
Isak

15
พระเยซู นั่นคือรหัสที่น่าเกลียด ทำไมรหัส async ไม่สามารถอ่านได้
John Shedletsky

ทำไมคุณต้องมีการร้องขอ BeginGetResponse () ทำไม wrapperAction.BeginInvoke () ไม่พอเพียง?
Igor Gatis

2
@Gatis มีการเรียกแบบอะซิงโครนัสสองระดับ - wrapperAction.BeginInvoke () เป็นการเรียกแบบอะซิงโครนัสครั้งแรกสำหรับนิพจน์แลมบ์ดาที่เรียกร้องการร้องขอ BeginGetResponse () ซึ่งเป็นการเรียกแบบอะซิงโครนัสครั้งที่สอง ตามที่ Isak ชี้ให้เห็น 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 )
Alex Klaus

วิธีง่ายกว่าวิธีอื่น ๆ
Don Rolling

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 เปลี่ยนไปเนื่องจากมีคำตอบจำนวนมากถูกโพสต์และฉันต้องการให้คำตอบที่เป็นปัจจุบันมากขึ้น ใช้วิธีการ async เพื่อเริ่มการทำงาน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;
}

วิธีใช้ async

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/ …
Pete Kirkham

@PeteKirkham เธรดพื้นหลังกำลังดำเนินการตามคำขอไม่ใช่เธรด UI เป้าหมายคือเพื่อหลีกเลี่ยงการบล็อกเธรด UI วิธีใดก็ตามที่คุณเลือกที่จะทำการร้องขอจะบล็อกเธรดที่ทำการร้องขอ ตัวอย่างของ Microsoft ที่คุณอ้างถึงพยายามที่จะทำการร้องขอหลายครั้ง แต่พวกเขายังคงสร้าง Task (เธรดพื้นหลัง) สำหรับการร้องขอ
tronman

3
เพื่อให้ชัดเจนนี่คือรหัสซิงโครนัส / การปิดกั้น 100% เพื่อใช้ async WebRequest.GetResponseAsync()และStreamReader.ReadToEndAync()จำเป็นต้องใช้และรอคอย
Richard Szalay

4
@tronman การเรียกใช้วิธีการบล็อกในงานเมื่อมีการเทียบเท่า async เป็นรูปแบบการต่อต้านแบบท้อแท้ แม้ว่ามันจะปลดบล็อกเธรดการโทร แต่ก็ไม่ทำอะไรเลยในการปรับขนาดสำหรับสถานการณ์การโฮสต์เว็บเนื่องจากคุณเพิ่งย้ายงานไปยังเธรดอื่นแทนที่จะใช้พอร์ต IO ที่สมบูรณ์เพื่อให้ได้แบบอะซิงโครนัส
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);
        } 
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.