【问题标题】:How to use HttpWebRequest (.NET) asynchronously?如何异步使用 HttpWebRequest (.NET)?
【发布时间】:2024-01-07 16:35:01
【问题描述】:

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

【问题讨论】:

标签: c# .net asynchronous httprequest


【解决方案1】:

使用HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;

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

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

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

【讨论】:

  • BeginGetResponse 对于异步使用没有多大用处。尝试联系资源时似乎被阻止。尝试拔下网络电缆或为其提供格式错误的 uri,然后运行此代码。相反,您可能需要在您提供的第二个线程上运行 GetResponse。
  • @AshleyHenderson - 你能给我一个样品吗?
  • @Tohid here is a full class with sample 我用过 Unity3D。
  • 您应该添加webRequest.Proxy = null 以显着加快请求速度。
  • C# 抛出一个错误,告诉我这是一个过时的类
【解决方案2】:

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

var request = WebRequest.Create("http://www.*.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);
    });

【讨论】:

【解决方案3】:

考虑答案:

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

问候

【讨论】:

  • +1 表示不会超出“请求”变量范围的选项,但您可以进行强制转换而不是使用“as”关键字。将抛出 InvalidCastException 而不是混淆 NullReferenceException
【解决方案4】:

到目前为止,每个人都错了,因为BeginGetResponse() 在当前线程上做了一些工作。来自documentation

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

【讨论】:

  • 你能不能只使用 await 调用 HttpWebRequest 的 GetResponseAsync 方法(假设你的函数是异步的)?我对 C# 很陌生,所以这可能完全是胡言乱语...
  • GetResponseAsync 看起来不错,但您需要 .NET 4.5(当前为测试版)。
  • 耶稣。那是一些丑陋的代码。为什么异步代码不可读?
  • 为什么需要 request.BeginGetResponse()?为什么 wrapperAction.BeginInvoke() 不够用?
  • @Gatis 有两个级别的异步调用 - wrapperAction.BeginInvoke() 是对调用 request.BeginGetResponse() 的 lambda 表达式的第一个异步调用,这是第二个异步调用。正如 Isak 指出的那样,BeginGetResponse() 需要一些同步设置,这就是他将其包装在一个额外的异步调用中的原因。
【解决方案5】:
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】:

    我最终使用了BackgroundWorker,它与上面的一些解决方案不同,它绝对是异步的,它为您处理返回GUI线程,并且非常容易理解。

    处理异常也很容易,因为它们最终会出现在 RunWorkerCompleted 方法中,但请务必阅读以下内容:Unhandled exceptions in 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】:

      自从发布了许多这些答案以来,.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() 而不是 WebRequest.GetResponse() 的 UWP 应用,并且它不会在适当的情况下调用 Dispose() 方法。 @dragansr 有一个很好的替代解决方案来解决这些问题。

      【讨论】:

      • 谢谢!一直在尝试找到一个异步示例,很多示例使用的是过于复杂的旧方法。
      • 这不会为每个响应阻塞一个线程吗?它似乎与例如有点不同。 docs.microsoft.com/en-us/dotnet/standard/parallel-programming/…
      • @PeteKirkham 后台线程正在执行请求,而不是 UI 线程。目标是避免阻塞 UI 线程。您选择发出请求的任何方法都会阻止发出请求的线程。您引用的 Microsoft 示例正在尝试发出多个请求,但它们仍在为请求创建任务(后台线程)。
      • 明确地说,这是 100% 同步/阻塞代码。要使用异步,WebRequest.GetResponseAsync()StreamReader.ReadToEndAync() 需要被使用和等待。
      • @tronman 在异步等效项可用时在任务中运行阻塞方法是一种非常不鼓励使用的反模式。虽然它确实解除了对调用线程的阻塞,但它对 Web 托管方案的规模没有任何作用,因为您只是将工作转移到另一个线程而不是使用 IO 完成端口来实现异步。
      【解决方案8】:
      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);
              } 
          }
      

      【讨论】:

        【解决方案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);
        });
        

        【讨论】:

          【解决方案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);
              }
          

          【讨论】: