如何异步使用 HttpWebRequest (.NET)?

How to use HttpWebRequest (.NET) asynchronously?

提问人:Jason 提问时间:10/15/2008 最后编辑:FerruccioJason 更新时间:1/18/2022 访问量:196630

问:

如何异步使用 HttpWebRequest(.NET、C#)?

C# .NET 异步 HTTP请求

评论

1赞 Raj Kaimal 3/27/2010
使用异步 msdn.microsoft.com/en-us/library/...
1赞 Kyle 5/20/2010
有那么一会儿,我想知道你是否在尝试评论递归线程?
0赞 Sean Sexton 4/9/2009
您还可以看到以下内容,这是执行 Jason 要求的非常完整的示例:stuff.seans.com/2009/01/05/...肖恩
2赞 10/15/2008
查看这篇关于 Developer Fusion 的文章:developerfusion.com/code/4654/asynchronous-httpwebrequest

答:

127赞 Jon B 10/15/2008 #1

使用 HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

异步操作完成时调用回调函数。您至少需要从此函数调用 EndGetResponse()。

评论

17赞 Ash 10/21/2012
BeginGetResponse 对于异步使用不是很有用。它似乎在尝试联系资源时被阻止。尝试拔下网线或为其提供格式错误的 uri,然后运行此代码。相反,您可能需要在提供的第二个线程上运行 GetResponse。
2赞 Tohid 11/14/2012
@AshleyHenderson - 你能给我一个样品吗?
1赞 cregox 5/28/2013
@Tohid这里是一个完整的类,其中包含我用于 Unity3D 的示例。
3赞 Trontor 10/30/2013
您应该添加以显着加快请求速度。webRequest.Proxy = null
0赞 AleX_ 8/3/2017
C#抛出一个错误,告诉我这是一个过时的类
67赞 xlarsx 12/14/2010 #2

考虑答案:

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赞 Davi Fiamenghi 7/1/2012
+1 表示不会超出“request”变量范围的选项,但您可以进行强制转换而不是使用“as”关键字。将引发 InvalidCastException,而不是令人困惑的 NullReferenceException
3赞 Sten Petrov 10/8/2012 #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);
        } 
    }
64赞 Isak 12/20/2012 #4

到目前为止,每个人都错了,因为在当前线程上做了一些工作。从文档中: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赞 Brad 2/21/2013
您不能使用 await 调用 HttpWebRequest 的 GetResponseAsync 方法吗(假设您使函数异步)?我对 C# 很陌生,所以这可能完全是胡说八道......
0赞 Isak 2/22/2013
GetResponseAsync 看起来不错,但需要 .NET 4.5(当前测试版)。
16赞 John Shedletsky 5/14/2013
耶稣。这是一些丑陋的代码。为什么异步代码不可读?
0赞 Igor Gatis 8/21/2015
你为什么需要请求。BeginGetResponse()?为什么wrapperAction.BeginInvoke()不够用?
2赞 walkingTarget 11/12/2015
@Gatis 异步调用有两个级别 - wrapperAction.BeginInvoke() 是对调用 request 的 lambda 表达式的第一个异步调用。BeginGetResponse(),这是第二个异步调用。正如 Isak 所指出的,BeginGetResponse() 需要一些同步设置,这就是为什么他将其包装在一个额外的异步调用中。
7赞 eggbert 3/7/2013 #5

我最终使用了 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();
72赞 Nathan Baulch 4/11/2014 #6

到目前为止,最简单的方法是使用 TPL 中的 TaskFactory.FromAsync。当与新的 async/await 关键字结合使用时,它实际上是几行代码:

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

评论

0赞 Alex Klaus 7/17/2015
从 .NET 4 开始,此 TAP 方法更可取。请参阅 MS 中的类似示例 - “How to: Wrap EAP Patterns in a Task” (msdn.microsoft.com/en-us/library/ee622454.aspx)
0赞 Don Rolling 4/9/2016
比其他方式容易得多
6赞 tronman 10/19/2017 #7

自从发布其中许多答案以来,.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;
}

要使用 async 方法,请执行以下操作:

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

更新:

此解决方案不适用于使用 代替 的 UWP 应用,并且不会在适当的情况下调用这些方法。@dragansr 有一个很好的替代解决方案来解决这些问题。WebRequest.GetResponseAsync()WebRequest.GetResponse()Dispose()

评论

1赞 WDUK 11/24/2017
谢谢!一直在尝试找到一个异步示例,很多示例使用过于复杂的旧方法。
0赞 Pete Kirkham 1/11/2018
这不会阻塞每个响应的线程吗?这似乎与 learn.microsoft.com/en-us/dotnet/standard/parallel-programming/ 大不相同......
0赞 tronman 1/13/2018
@PeteKirkham 后台线程正在执行请求,而不是 UI 线程。目标是避免阻塞 UI 线程。您选择发出请求的任何方法都将阻止发出请求的线程。您引用的 Microsoft 示例正在尝试发出多个请求,但他们仍在为请求创建任务(后台线程)。
3赞 Richard Szalay 3/12/2018
需要明确的是,这是 100% 同步/阻塞代码。要使用 async,并且需要使用和等待。WebRequest.GetResponseAsync()StreamReader.ReadToEndAync()
4赞 Richard Szalay 3/14/2018
@tronman 当异步等效项可用时,在任务中运行阻塞方法是一种强烈建议的反模式。虽然它确实会取消阻止调用线程,但它对 Web 托管方案的缩放没有任何作用,因为你只是将工作移动到另一个线程,而不是使用 IO 完成端口来实现异步。
9赞 dragansr 3/12/2018 #8
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);
}
0赞 Raiio 12/23/2021 #9

跟进@Isak的回答,很不错。尽管如此,它最大的缺陷是,只有当响应的状态为 200-299 时,它才会调用 responseAction。解决此问题的最佳方法是:

private void DoWithResponseAsync(HttpWebRequest request, Action<HttpWebResponse> responseAction)
{
    Action wrapperAction = () =>
    {
        request.BeginGetResponse(new AsyncCallback((iar) =>
        {
            HttpWebResponse response;
            try
            {
                response = (HttpWebResponse)((HttpWebRequest)iar.AsyncState).EndGetResponse(iar);
            }
            catch (WebException ex)
            {
                // It needs to be done like this in order to read responses with error status:
                response = ex.Response as HttpWebResponse;
            }
            responseAction(response);
        }), request);
    };
    wrapperAction.BeginInvoke(new AsyncCallback((iar) =>
    {
        var action = (Action)iar.AsyncState;
        action.EndInvoke(iar);
    }), wrapperAction);
}

然后如下@Isak:

HttpWebRequest request;
// init your request...then:
DoWithResponse(request, (response) => {
    var body = new StreamReader(response.GetResponseStream()).ReadToEnd();
    Console.Write(body);
});
-1赞 Jacksonkr 1/18/2022 #10

我一直在将其用于异步 UWR,希望它对某人有所帮助

    string uri = "http://some.place.online";

    using (UnityWebRequest uwr = UnityWebRequest.Get(uri))
    {
        var asyncOp = uwr.SendWebRequest();
        while (asyncOp.isDone == false) await Task.Delay(1000 / 30); // 30 hertz

        if(uwr.result == UnityWebRequest.Result.Success) return uwr.downloadHandler.text;
        Debug.LogError(uwr.error);
    }