System.Net.Http.HttpClient.PostAsync 块,从不 returns
System.Net.Http.HttpClient.PostAsync blocks and never returns
我有一个 .NET 框架 Windows Forms 应用程序,其表单具有以下代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace test
{
public partial class Main : Form
{
public int exitCode = 1;
private Options opts;
CancellationTokenSource cancellationSource = new CancellationTokenSource();
public Main(Options opts)
{
InitializeComponent();
this.opts = opts;
}
private void btnCancel_Click(object sender, EventArgs e)
{
exitCode = 1;
cancellationSource.Cancel();
Close();
}
async Task doUpload()
{
using (var content = new MultipartFormDataContent())
{
List<FileStream> streams = new List<FileStream>();
try
{
foreach (string fPath in opts.InputFiles)
{
FileStream stream = new FileStream(fPath, FileMode.Open, FileAccess.Read);
streams.Add(stream);
content.Add(new StreamContent(stream), fPath);
}
var progressContent = new ProgressableStreamContent(
content,
4096,
(sent, total) =>
{
double percent = 100 * sent / total;
progressBar.Value = (int)percent;
});
using (var client = new HttpClient())
{
using (var response = await client.PostAsync(opts.URL, progressContent, cancellationSource.Token))
{
if (response.IsSuccessStatusCode)
{
exitCode = 0;
}
else
{
MessageBox.Show(
response.Content.ToString(),
"Error " + response.StatusCode,
MessageBoxButtons.OK, MessageBoxIcon.Error
);
}
Close();
}
}
}
finally
{
foreach (FileStream stream in streams)
{
stream.Close();
}
}
}
}
private void Main_Load(object sender, EventArgs e)
{
}
private void Main_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = !cancellationSource.IsCancellationRequested;
}
private void Main_Shown(object sender, EventArgs e)
{
doUpload();
}
}
}
ProgressableStreamContent 与此处给出的相同:
问题是永远不会返回响应。换句话说:等待 postAsync 永远不会完成。此外,永远不会回调进度回调。即使我尝试使用包含不存在域的 POST URL,也没有任何反应。我想这是一个僵局,但我不明白怎么办?异步任务的结果永远不会在任何地方使用,也不会等待。
它与An async/await example that causes a deadlock不同,因为没有使用 .Result 并且从未等待该方法,而且调用 ConfigureAwait(false) 似乎没有效果。
更新:我已经为这个问题创建了一个新的github repo,所以任何人都可以测试它:
https://github.com/nagylzs/csharp_http_post_example
更新:终于成功了。不需要 ConfigureAwait。所有 UI 更新操作都必须放在 Invoke 中。我已将测试回购更新为工作版本。还添加了 TLSv1.2 支持(默认情况下禁用)。
你需要改变这个:
client.PostAsync(opts.URL, progressContent, cancellationSource.Token)
至
client.PostAsync(opts.URL, progressContent, cancellationSource.Token).ConfigureAwait(false)
这已经讨论过了,所以您可以在网上找到其他资源,但是 this 应该是一个很好的起点。
您发布的代码中的 PostAsync
不会阻塞(但它实际上从来没有 returns !)。它抛出异常:
System.InvalidOperationException: Cross-thread operation not valid: Control 'progressBar' accessed from a thread other than the thread it was created on.
这就是断点对您不起作用的原因。正确的解决方案是:
var progressContent = new ProgressableStreamContent(
content,
4096,
(sent, total) =>
{
Invoke((Action) (() => {
double percent = 100 * sent / total;
progressBar.Value = (int) percent;
}));
});
(添加 Invoke
或 BeginInvoke
到回调)
HTTP 客户端的回调在后台线程上调用,如果您希望它们访问您的 UI 控件,您必须将它们放入 window 的偶数队列中。
.ConfigureAwait(false)
与此问题无关,您不应在 UI 上下文中使用它(恰恰相反:您 想要 将延续放在 UI 线程上,所以你 不应该 使用它)。
我有一个 .NET 框架 Windows Forms 应用程序,其表单具有以下代码:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace test
{
public partial class Main : Form
{
public int exitCode = 1;
private Options opts;
CancellationTokenSource cancellationSource = new CancellationTokenSource();
public Main(Options opts)
{
InitializeComponent();
this.opts = opts;
}
private void btnCancel_Click(object sender, EventArgs e)
{
exitCode = 1;
cancellationSource.Cancel();
Close();
}
async Task doUpload()
{
using (var content = new MultipartFormDataContent())
{
List<FileStream> streams = new List<FileStream>();
try
{
foreach (string fPath in opts.InputFiles)
{
FileStream stream = new FileStream(fPath, FileMode.Open, FileAccess.Read);
streams.Add(stream);
content.Add(new StreamContent(stream), fPath);
}
var progressContent = new ProgressableStreamContent(
content,
4096,
(sent, total) =>
{
double percent = 100 * sent / total;
progressBar.Value = (int)percent;
});
using (var client = new HttpClient())
{
using (var response = await client.PostAsync(opts.URL, progressContent, cancellationSource.Token))
{
if (response.IsSuccessStatusCode)
{
exitCode = 0;
}
else
{
MessageBox.Show(
response.Content.ToString(),
"Error " + response.StatusCode,
MessageBoxButtons.OK, MessageBoxIcon.Error
);
}
Close();
}
}
}
finally
{
foreach (FileStream stream in streams)
{
stream.Close();
}
}
}
}
private void Main_Load(object sender, EventArgs e)
{
}
private void Main_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = !cancellationSource.IsCancellationRequested;
}
private void Main_Shown(object sender, EventArgs e)
{
doUpload();
}
}
}
ProgressableStreamContent 与此处给出的相同:
问题是永远不会返回响应。换句话说:等待 postAsync 永远不会完成。此外,永远不会回调进度回调。即使我尝试使用包含不存在域的 POST URL,也没有任何反应。我想这是一个僵局,但我不明白怎么办?异步任务的结果永远不会在任何地方使用,也不会等待。
它与An async/await example that causes a deadlock不同,因为没有使用 .Result 并且从未等待该方法,而且调用 ConfigureAwait(false) 似乎没有效果。
更新:我已经为这个问题创建了一个新的github repo,所以任何人都可以测试它:
https://github.com/nagylzs/csharp_http_post_example
更新:终于成功了。不需要 ConfigureAwait。所有 UI 更新操作都必须放在 Invoke 中。我已将测试回购更新为工作版本。还添加了 TLSv1.2 支持(默认情况下禁用)。
你需要改变这个:
client.PostAsync(opts.URL, progressContent, cancellationSource.Token)
至
client.PostAsync(opts.URL, progressContent, cancellationSource.Token).ConfigureAwait(false)
这已经讨论过了,所以您可以在网上找到其他资源,但是 this 应该是一个很好的起点。
PostAsync
不会阻塞(但它实际上从来没有 returns !)。它抛出异常:
System.InvalidOperationException: Cross-thread operation not valid: Control 'progressBar' accessed from a thread other than the thread it was created on.
这就是断点对您不起作用的原因。正确的解决方案是:
var progressContent = new ProgressableStreamContent(
content,
4096,
(sent, total) =>
{
Invoke((Action) (() => {
double percent = 100 * sent / total;
progressBar.Value = (int) percent;
}));
});
(添加 Invoke
或 BeginInvoke
到回调)
HTTP 客户端的回调在后台线程上调用,如果您希望它们访问您的 UI 控件,您必须将它们放入 window 的偶数队列中。
.ConfigureAwait(false)
与此问题无关,您不应在 UI 上下文中使用它(恰恰相反:您 想要 将延续放在 UI 线程上,所以你 不应该 使用它)。