System.Net.Http.HttpClient.PostAsync блокирует и никогда не возвращает - PullRequest
2 голосов
/ 13 октября 2019

У меня есть .NET Framework приложение 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 такой же, как здесь: C #: HttpClient, прогресс загрузки файла призагрузка нескольких файлов как MultipartFormDataContent

Проблема заключается в том, что ответ никогда не возвращается. Другими словами: ожидание postAsync никогда не завершается. Кроме того, обратный вызов прогресса никогда не перезванивается. Даже если я попытаюсь использовать POST-URL, который содержит нерасходный домен, ничего не происходит. Я думаю, это тупик, но я не понимаю, как? Результат асинхронной задачи никогда нигде не используется и не ожидается.

Он отличается от Пример асинхронного ожидания / ожидания, который вызывает взаимоблокировку , поскольку .Result не используется, а методникогда не ожидал, а также кажется, что вызов ConfigureAwait (false) не имеет никакого эффекта.

ОБНОВЛЕНИЕ : я создал новое репозиторий github для этого вопроса, поэтому любой может его проверить:

https://github.com/nagylzs/csharp_http_post_example

ОБНОВЛЕНИЕ : Наконец-то все работает. ConfigureAwait не нужен. Все операции обновления пользовательского интерфейса должны быть помещены в Invoke. Я обновил тестовое репо до рабочей версии. Также добавлена ​​поддержка TLSv1.2 (которая по умолчанию отключена).

Ответы [ 2 ]

2 голосов
/ 16 октября 2019

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

(либо добавьте Invoke или BeginInvoke к обратному вызову)

Обратные вызовы клиента HTTP вызываются в фоновом потоке, и вынужно поместить их в четную очередь вашего окна, если вы хотите, чтобы они получили доступ к элементам управления вашего пользовательского интерфейса.

.ConfigureAwait(false) не имеет никакого отношения к этой проблеме, вы не должны использовать его в контексте пользовательского интерфейса (как раз наоборот:Вы хотите , чтобы поместить продолжение в поток пользовательского интерфейса, поэтому не следует использовать его).

0 голосов
/ 13 октября 2019

Вам нужно изменить это:

client.PostAsync(opts.URL, progressContent, cancellationSource.Token)

на

client.PostAsync(opts.URL, progressContent, cancellationSource.Token).ConfigureAwait(false)

Это уже обсуждалось, поэтому вы можете найти дополнительные ресурсы в сети, но это должно быть хорошей отправной точкой.

...