首页 > 解决方案 > System.Net.Http.HttpClient.PostAsync 阻塞且永不返回

问题描述

我有一个带有以下代码的表单的 .NET 框架 Windows 窗体应用程序:

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 与此处给出的相同:C#: HttpClient, File upload progress when uploading multiple file as MultipartFormDataContent

问题是永远不会返回响应。换句话说:等待 postAsync 永远不会完成。此外,进度回调永远不会被回调。即使我尝试使用包含不存在域的 POST URL,也不会发生任何事情。我想这是一个僵局,但我不明白如何?异步任务的结果永远不会在任何地方使用,也不会等待。

它与导致死锁的异步/等待示例不同,因为未使用 .Result 并且从不等待该方法,而且似乎调用 ConfigureAwait(false) 没有效果。

更新:我为这个问题创建了一个新的 github repo,所以任何人都可以测试它:

https://github.com/nagylzs/csharp_http_post_example

更新:终于可以了。不需要配置等待。所有 UI 更新操作都必须放在 Invoke 中。我已将测试存储库更新为工作版本。还添加了 TLSv1.2 支持(默认禁用)。

标签: c#async-awaitdotnet-httpclient

解决方案


PostAsync在您发布的代码中不会阻塞(但它实际上永远不会返回!)。它抛出一个异常:

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

(添加InvokeBeginInvoke回调)

HTTP 客户端的回调在后台线程上调用,如果您希望它们访问您的 UI 控件,则必须将它们放入窗口的偶数队列中。

.ConfigureAwait(false)与这个问题无关,你不应该在 UI 上下文中使用它(恰恰相反:你希望它把延续放到 UI 线程上,所以你不应该使用它)。


推荐阅读