如何在不中断异步/等待的情况下创建 HttpWebRequest? [英] How to create HttpWebRequest without interrupting async/await?

查看:39
本文介绍了如何在不中断异步/等待的情况下创建 HttpWebRequest?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一堆缓慢的函数,本质上是这样的:

I have a bunch of slow functions that are essentially this:

private async Task<List<string>> DownloadSomething()
{
    var request = System.Net.WebRequest.Create("https://valid.url");

    ...

    using (var ss = await request.GetRequestStreamAsync())
    { 
        await ss.WriteAsync(...);
    }

    using (var rr = await request.GetResponseAsync())
    using (var ss = rr.GetResponseStream())
    {
        //read stream and return data
    }

}

除了对 WebRequest.Create 的调用之外,这可以很好地异步工作 - 这一行将 UI 线程冻结了几秒钟,这在某种程度上破坏了 async/await 的目的.

This works nicely and asynchronously except for the call to WebRequest.Create - this single line freezes the UI thread for several seconds which sort of ruins the purpose of async/await.

我已经使用 BackgroundWorkers 编写了这段代码,它运行良好且不会冻结 UI.
不过,关于 async/await 创建 Web 请求的正确、惯用的方法是什么? 或者也许应该使用另一个类?

I already have this code written using BackgroundWorkers, which works perfectly and never freezes the UI.
Still, what is the correct, idiomatic way to create a web request with respect to async/await? Or maybe there is another class that should be used?

我已经看到 这个不错的答案关于异步化 WebRequest,但即使有对象本身是同步创建的.

I've seen this nice answer about asyncifying a WebRequest, but even there the object itself is created synchronously.

推荐答案

有趣的是,我没有看到 WebRequest.CreateHttpClient.PostAsync 的阻塞延迟.这可能与 DNS 解析或代理配置有关,尽管我希望这些操作也能在内部以异步方式实现.

Interestingly, I'm not seeing a blocking delay with WebRequest.Create or HttpClient.PostAsync. It might be something to do with DNS resolution or proxy configuration, although I'd expect these operations to be implemented internally as asynchronous, too.

无论如何,作为解决方法,您可以在池线程上启动请求,尽管我通常不会这样做:

Anyway, as a workaround you can start the request on a pool thread, although this is not something I'd normally do:

private async Task<List<string>> DownloadSomething()
{
    var request = await Task.Run(() => {
        // WebRequest.Create freezes??
        return System.Net.WebRequest.Create("https://valid.url");
    });

    // ...

    using (var ss = await request.GetRequestStreamAsync())
    { 
        await ss.WriteAsync(...);
    }

    using (var rr = await request.GetResponseAsync())
    using (var ss = rr.GetResponseStream())
    {
        //read stream and return data
    }
}

这将使 UI 保持响应,但如果用户想要停止操作,可能很难取消.那是因为您需要已经有一个 WebRequest 实例才能对其调用 Abort.

That would keep the UI responsive, but it might be difficult to cancel it if user wants to stop the operation. That's because you need to already have a WebRequest instance to be able to call Abort on it.

使用 HttpClient,取消是可能的,就像这样:

Using HttpClient, cancellation would be possible, something like this:

private async Task<List<string>> DownloadSomething(CancellationToken token)
{
    var httpClient = new HttpClient();

    var response = await Task.Run(async () => {
        return await httpClient.PostAsync("https://valid.url", token);
    }, token);

    // ...
}

使用 HttpClient,您还可以在取消令牌上注册 httpClient.CancelPendingRequests() 回调,例如 这个.<小时>[UPDATE] 根据评论:在您的原始案例中(在引入 Task.Run 之前)您可能不需要 IProgress图案.只要在 UI 线程上调用 DownloadSomething()DownloadSomething 中每个 await 之后的每个执行步骤都会在同一个 UI 线程上恢复,因此您可以在 await 之间直接更新 UI.

With HttpClient, you can also register a httpClient.CancelPendingRequests() callback on the cancellation token, like this.


[UPDATE] Based on the comments: in your original case (before introducing Task.Run) you probably did not need the IProgress<I> pattern. As long as DownloadSomething() was called on the UI thread, every execution step after each await inside DownloadSomething would be resumed on the same UI thread, so you could just update the UI directly in between awaits.

现在,要在池线程上通过 Task.Run 运行整个 DownloadSomething(),您必须传递 IProgress 的实例; 放入其中,例如:

Now, to run the whole DownloadSomething() via Task.Run on a pool thread, you would have to pass an instance of IProgress<I> into it, e.g.:

private async Task<List<string>> DownloadSomething(
    string url, 
    IProgress<int> progress, 
    CancellationToken token)
{
    var request = System.Net.WebRequest.Create(url);

    // ...

    using (var ss = await request.GetRequestStreamAsync())
    { 
        await ss.WriteAsync(...);
    }

    using (var rr = await request.GetResponseAsync())
    using (var ss = rr.GetResponseStream())
    {
        // read stream and return data
        progress.Report(...); // report progress  
    }
}

// ...

// Calling DownloadSomething from the UI thread via Task.Run:

var progressIndicator = new Progress<int>(ReportProgress);
var cts = new CancellationTokenSource(30000); // cancel in 30s (optional)
var url = "https://valid.url";
var result = await Task.Run(() => 
    DownloadSomething(url, progressIndicator, cts.Token), cts.Token);
// the "result" type is deduced to "List<string>" by the compiler 

注意,因为 DownloadSomething 本身就是一个 async 方法,它现在作为嵌套任务运行,Task.Run 透明地为你.更多相关信息:Task.Run vs Task.Factory.开始新的.

Note, because DownloadSomething is an async method itself, it is now run as a nested task, which Task.Run transparently unwraps for you. More on this: Task.Run vs Task.Factory.StartNew.

另请查看:在异步 API 中启用进度和取消.

这篇关于如何在不中断异步/等待的情况下创建 HttpWebRequest?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆