c# - 如何在不中断 async/await 的情况下创建 HttpWebRequest?

标签 c# httpwebrequest .net-4.5 async-await

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

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 的目的。

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

我见过this nice answer关于异步 WebRequest,但即使如此,对象本身也是同步创建的。

最佳答案

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

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

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 保持响应,但可能很难 cancel如果用户想停止操作。那是因为你需要已经有一个 WebRequest实例能够调用Abort就在上面。

使用HttpClient ,可以取消,如下所示:

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()取消 token 的回调,例如 this

<小时/> [更新] 根据评论:在您原来的情况下(在引入 Task.Run 之前),您可能不需要 IProgress<I>图案。只要DownloadSomething()在 UI 线程上调用,每个 await 之后的每个执行步骤里面DownloadSomething将在同一个 UI 线程上恢复,因此您可以直接在 awaits 之间更新 UI .

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

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 

注意,因为DownloadSomethingasync方法本身,它现在作为嵌套任务运行,Task.Run透明地为您打开。更多信息:Task.Run vs Task.Factory.StartNew .

另请查看:Enabling Progress and Cancellation in Async APIs .

关于c# - 如何在不中断 async/await 的情况下创建 HttpWebRequest?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21007112/

相关文章:

.net - 如何在传输级别压缩来自 WCF .NET 的 HTTP 请求?

C# 静态方法内联

c# - 错误 "; expected"lambda 表达式

c# - 在多个类别中使用一个 Random 对象是否会降低每个类别的随机性?

c# - 为什么将 DateTime 格式化为字符串会截断而不是舍入毫秒?

字符串中的 C# Razor 语法

c# - System.Net.HttpWebResponse.GetResponseStream() 在 WebException 中返回截断的正文

c# - 在多个 HttpWebRequest 中使用相同的 CookieContainer 是否安全?

.net - .Net 4.5 中的 WinRT API

c# - 从 Ilist 获取 Idictionnary