Làm cách nào để sử dụng không đồng bộ httpWebRequest (.NET)?


156

Làm cách nào tôi có thể sử dụng không đồng bộ httpWebRequest (.NET, C #)?


1
Kiểm tra bài viết này về Nhà phát triển Fusion: developerfusion.com/code/4654/asynyncous-httpwebrequest

Bạn cũng có thể xem những điều sau đây, ví dụ khá đầy đủ về việc làm những gì Jason đang yêu cầu: Stuff.seans.com/2009/01/05/ Khăn Sean
Sean Sexton


1
Trong một khoảnh khắc, tôi tự hỏi nếu bạn đang cố gắng nhận xét về một chủ đề đệ quy?
Kyle Hodgson

Câu trả lời:


125

Sử dụng HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

Hàm gọi lại được gọi khi hoạt động không đồng bộ hoàn tất. Bạn cần ít nhất gọi EndGetResponse()từ chức năng này.


16
BeginGetResponse không hữu ích cho việc sử dụng async. Nó dường như chặn trong khi cố gắng liên hệ với tài nguyên. Hãy thử rút cáp mạng của bạn hoặc cho nó một uri không đúng định dạng, sau đó chạy mã này. Thay vào đó, bạn có thể cần chạy GetResponse trên luồng thứ hai mà bạn cung cấp.
Tro

2
@AshleyHenderson - Bạn có thể vui lòng cung cấp cho tôi một mẫu?
Tohid

1
@Tohid ở đây là một lớp đầy đủ với mẫu tôi đã sử dụng với Unity3D.
cregox

3
Bạn nên thêm webRequest.Proxy = nullđể tăng tốc yêu cầu đáng kể.
Trontor

C # đưa ra một lỗi cho tôi biết rằng đây là một lớp lỗi thời
AleX_

67

Xem xét câu trả lời:

HttpWebRequest webRequest;

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

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

Bạn có thể gửi con trỏ yêu cầu hoặc bất kỳ đối tượng nào khác như thế này:

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

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

Lời chào hỏi


7
+1 cho tùy chọn không vượt quá phạm vi biến 'yêu cầu', nhưng bạn có thể đã thực hiện thay vì sử dụng từ khóa "dưới dạng". Một UnlimitedCastException sẽ bị ném thay vì nhầm lẫn NullReferenceException
Davi Fiamenghi

64

Tất cả mọi người cho đến nay đã sai, bởi vì BeginGetResponse()một số làm việc trên chủ đề hiện tại. Từ tài liệu :

Phương thức BeginGetResponse yêu cầu một số tác vụ thiết lập đồng bộ để hoàn thành (ví dụ: độ phân giải DNS, phát hiện proxy và kết nối ổ cắm TCP) trước khi phương thức này trở nên không đồng bộ. Do đó, phương thức này không bao giờ được gọi trên luồng giao diện người dùng (UI) vì có thể mất nhiều thời gian (tối đa vài phút tùy thuộc vào cài đặt mạng) để hoàn thành các tác vụ thiết lập đồng bộ ban đầu trước khi ném ngoại lệ hoặc lỗi phương pháp thành công.

Vì vậy, để làm điều này đúng:

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

Sau đó, bạn có thể làm những gì bạn cần với phản ứng. Ví dụ:

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

2
Bạn có thể không chỉ gọi phương thức GetResponseAsync của HttpWebRequest bằng cách chờ đợi (giả sử bạn đã thực hiện chức năng của mình không đồng bộ)? Tôi đã rất mới với C # vì vậy điều này có thể hoàn toàn vô nghĩa ...
Brad

GetResponseAsync có vẻ tốt, mặc dù bạn sẽ cần .NET 4.5 (hiện đang là beta).
Isak

15
Chúa Giêsu. Đó là một số mã xấu xí. Tại sao mã async không thể đọc được?
John Shedletsky

Tại sao bạn cần request.BeginGetResponse ()? Tại sao WrapperAction.BeginInvoke () không đủ?
Igor Gatis

2
@Gatis Có hai cấp độ của cuộc gọi không đồng bộ - WrapperAction.BeginInvoke () là cuộc gọi không đồng bộ đầu tiên đến biểu thức lambda gọi request.BeginGetResponse (), đây là cuộc gọi không đồng bộ thứ hai. Như Isak chỉ ra, BeginGetResponse () yêu cầu một số thiết lập đồng bộ, đó là lý do tại sao anh ta kết thúc nó trong một cuộc gọi không đồng bộ bổ sung.
walkTarget

64

Cho đến nay, cách dễ nhất là sử dụng TaskFactory.FromAsync từ TPL . Đó thực sự là một vài dòng mã khi được sử dụng cùng với các từ khóa async / await mới :

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

Nếu bạn không thể sử dụng trình biên dịch C # 5 thì có thể thực hiện các thao tác trên bằng cách sử dụng phương thức Task.CContueWith :

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

Vì .NET 4 nên cách tiếp cận TAP này là thích hợp hơn. Xem một ví dụ tương tự từ MS - "Cách: Bọc các mẫu EAP trong một nhiệm vụ" ( msdn.microsoft.com/en-us/l Library / ee622454.aspx )
Alex Klaus

Cách dễ dàng hơn các cách khác
Don

8

Tôi đã kết thúc bằng BackgroundWorker, nó hoàn toàn không đồng bộ không giống như một số giải pháp trên, nó xử lý trả về chủ đề GUI cho bạn và điều đó rất dễ hiểu.

Cũng rất dễ xử lý các trường hợp ngoại lệ, vì chúng kết thúc bằng phương thức RunWorkerCompleted, nhưng hãy đảm bảo bạn đã đọc điều này: Các ngoại lệ chưa được xử lý trong BackgroundWorker

Tôi đã sử dụng WebClient nhưng rõ ràng bạn có thể sử dụng HttpWebRequest.GetResponse nếu bạn muốn.

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 đã thay đổi do nhiều câu trả lời trong số này đã được đăng và tôi muốn cung cấp câu trả lời cập nhật hơn. Sử dụng phương thức async để bắt đầu Taskchạy trên luồng nền:

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

Để sử dụng phương thức async:

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

Cập nhật:

Giải pháp này không hoạt động đối với các ứng dụng UWP sử dụng WebRequest.GetResponseAsync()thay thế WebRequest.GetResponse()và nó không gọi các Dispose()phương thức khi thích hợp. @dragansr có một giải pháp thay thế tốt giải quyết những vấn đề này.


1
Cảm ơn bạn ! Đã cố gắng tìm một ví dụ không đồng bộ, rất nhiều ví dụ sử dụng phương pháp cũ quá phức tạp.
WDUK

Điều này sẽ không chặn một chủ đề cho mỗi phản ứng? nó có vẻ hơi khác một chút so với ví dụ docs.microsoft.com/en-us/dotnet/st Chuẩn / vô vi
Pete Kirkham

@PeteKirkham Một luồng nền đang thực hiện yêu cầu, không phải luồng UI. Mục tiêu là để tránh chặn luồng UI. Bất kỳ phương pháp nào bạn chọn để thực hiện một yêu cầu sẽ chặn luồng thực hiện yêu cầu. Ví dụ về Microsoft mà bạn đề cập đến đang cố gắng thực hiện nhiều yêu cầu, nhưng họ vẫn đang tạo một Tác vụ (một luồng nền) cho các yêu cầu.
tronman

3
Để rõ ràng, đây là mã đồng bộ / chặn 100%. Để sử dụng async, WebRequest.GetResponseAsync()StreamReader.ReadToEndAync()cần được sử dụng và chờ đợi.
Richard Szalay

4
@tronman Chạy các phương thức chặn trong Tác vụ khi có sẵn các tương đương async là một kiểu chống không được khuyến khích cao. Mặc dù nó bỏ chặn luồng gọi, nhưng nó không có gì cho quy mô cho các tình huống lưu trữ web vì bạn chỉ chuyển công việc sang luồng khác thay vì sử dụng các cổng hoàn thành IO để đạt được sự không đồng bộ.
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);
        } 
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.