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


156

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


1
在Developer Fusion上查看以下文章:developerfusion.com/code/4654/asynchronous-httpwebrequest

您还可以看到以下内容,作为做杰森问的一个相当完整的示例:stuff.seans.com/2009/01/05/…肖恩
肖恩·塞克斯顿2009年


1
一会儿,我想知道您是否要在递归线程上发表评论?
凯尔·霍奇森

Answers:


125

HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

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


16
BeginGetResponse对于异步用法不是很有用。尝试联系资源时似乎阻塞了。尝试拔下网络电缆或给它提供格式错误的uri,然后运行此代码。相反,您可能需要在提供的第二个线程上运行GetResponse。
2012年

2
@AshleyHenderson-能否请您提供样品?
Tohid 2012年


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表示不会超出'request'变量的选项,但您可以进行强制转换,而不使用“ as”关键字。将抛出InvalidCastException而不是造成混乱的NullReferenceException
Davi Fiamenghi 2012年

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
您能否不使用await调用HttpWebRequest的GetResponseAsync方法(假设您使函数异步)?我对C#非常陌生,所以这可能完全是胡言乱语
Brad

GetResponseAsync看起来不错,尽管您需要.NET 4.5(当前为beta)。
Isak

15
耶稣。那是一些丑陋的代码。为什么异步代码不可读?
John Shedletsky

为什么需要request.BeginGetResponse()?为什么wrapperAction.BeginInvoke()不够用?
伊戈尔·加的斯

2
@Gatis有两个级别的异步调用-wrapperAction.BeginInvoke()是对lambda表达式的第一个异步调用,该lambda表达式调用request.BeginGetResponse(),这是第二个异步调用。正如Isak所指出的那样,BeginGetResponse()需要一些同步设置,这就是为什么他将其包装在另一个异步调用中的原因。
walkTarget

64

到目前为止,最简单的方法是使用TaskFactory.FromAsyncTPL。与新的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);
    });

由于.NET 4最好使用这种TAP方法。请参阅从MS一个类似的例子- “如何:裹EAP模式的任务”(msdn.microsoft.com/en-us/library/ee622454.aspx
亚历克斯·克劳斯

比其他方式容易得多
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发生了变化,我想提供一个最新的答案。使用异步方法来启动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/");

更新:

该解决方案不适用于使用WebRequest.GetResponseAsync()而不是的UWP应用WebRequest.GetResponse(),并且不会Dispose()在适当的地方调用方法。@dragansr有一个很好的替代解决方案,可以解决这些问题。


1
谢谢 !一直在尝试寻找一个异步示例,许多示例都使用了过于复杂的旧方法。
WDUK

这不会为每个响应阻塞线程吗?似乎与docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
Pete Kirkham,

@PeteKirkham后台线程在执行请求,而不是UI线程。目的是避免阻塞UI线程。您选择发出请求的任何方法都会阻塞发出请求的线程。您引用的Microsoft示例正在尝试发出多个请求,但它们仍在为这些请求创建任务(后台线程)。
tronman

3
需要明确的是,这是100%同步/阻塞代码。要使用异步,WebRequest.GetResponseAsync()并且StreamReader.ReadToEndAync()需要使用和等待。
理查德·萨雷

4
@tronman在异步等效项可用时在任务中运行阻止方法是强烈建议使用的反模式。尽管它确实解除了对调用线程的阻塞,但是对于网络托管方案而言,它并没有做任何扩展,因为您只是将工作移至另一个线程,而不是使用IO完成端口来实现异步。
理查德·萨雷

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);
        } 
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.