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

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

192596 次浏览

使用 HttpWebRequest.BeginGetResponse()

HttpWebRequest webRequest;


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


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

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

考虑一下答案:

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

你好

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

到目前为止,每个人都错了,因为 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);
});

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

处理异常也非常容易,因为它们最终会出现在 RunWorkerCompleted 方法中,但是请确保读取以下内容: BackoundWorker 中的未处理异常

我使用了 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();

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

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

.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 有一个很好的解决方案来解决这些问题。

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

跟进@Isak 的回答,这是非常好的。尽管如此,它最大的缺陷是只有在响应状态为200-299时才会调用 response Action。解决这个问题的最好办法是:

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

我一直在使用这个异步 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);
}