UploadFileAsync не асинхронный? - PullRequest
0 голосов
/ 02 мая 2010

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

Моя проблема в основном то, что я сказал в названии. Что происходит, так это то, что графический интерфейс зависает во время загрузки. Мой код:

// stuff above snipped

public partial class Form1 : Form
{
    WebClient wcUploader = new WebClient();

    public Form1()
    {
        InitializeComponent();

        wcUploader.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadFileCompletedCallback);
        wcUploader.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadProgressCallback);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        if (openFileDialog1.ShowDialog() == DialogResult.OK)
        {
            string toUpload = openFileDialog1.FileName;
            wcUploader.UploadFileAsync(new Uri("http://anyhub.net/api/upload"), "POST", toUpload);
        }
    }

    void UploadFileCompletedCallback(object sender, UploadFileCompletedEventArgs e)
    {
        textBox1.Text = System.Text.Encoding.UTF8.GetString(e.Result);
    }

    void UploadProgressCallback(object sender, UploadProgressChangedEventArgs e)
    {
        textBox1.Text = (string)e.UserState + "\n\n"
            + "Uploaded " + e.BytesSent + "/" + e.TotalBytesToSend + "b (" + e.ProgressPercentage + "%)";
    }
}

РЕДАКТИРОВАТЬ: Для пояснения, это то, что происходит по порядку:

  1. Я нажимаю кнопку1
  2. Я выбираю файл
  3. Графический интерфейс перестает отвечать на запросы, так как, когда я нажимаю на него, ничего не происходит
  4. Через пару секунд в текстовом поле появляется 50% Aa и реализация попадает. Смотрите мой комментарий к вопросу, который я пометил как решение
  5. Через секунду или около того, когда графический интерфейс не отвечает между ними, он заменяется ответом

Ответы [ 2 ]

4 голосов
/ 02 мая 2010

Конечно, это так.

Код работает просто отлично.

wcUploader.UploadFileAsync(...) инициирует запрос, и выполнение продолжается, а ход выполнения обновляется в TextBox1, и по завершении я получаю немного JSON.

Это Асинк. Если вы просто позвоните wcUploader.UploadFile, выполнение будет блокироваться там до тех пор, пока файл не будет загружен, и вы не получите никаких событий прогресса.

Итог:

Пользовательский интерфейс не заблокирован, вызываются события прогресса и пользовательский интерфейс обновляется в режиме реального времени.

Обновление:

Чтобы устранить начальный блок, когда веб-клиент устанавливает http-соединение, просто вызовите загрузку в другом потоке. В этом случае вы должны использовать вызов для предотвращения исключений между потоками:

using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private readonly WebClient wcUploader = new WebClient();

        public Form1()
        {
            InitializeComponent();

            wcUploader.UploadFileCompleted += UploadFileCompletedCallback;
            wcUploader.UploadProgressChanged += UploadProgressCallback;
        }


        private void UploadFileCompletedCallback(object sender, UploadFileCompletedEventArgs e)
        {
            // a clever way to handle cross-thread calls and avoid the dreaded
            // "Cross-thread operation not valid: Control 'textBox1' accessed 
            // from a thread other than the thread it was created on." exception

            // this will always be called from another thread,
            // no need to check for InvokeRequired
            BeginInvoke(
                new MethodInvoker(() =>
                    {
                        textBox1.Text = Encoding.UTF8.GetString(e.Result);
                        button1.Enabled = true;
                    }));
        }

        private void UploadProgressCallback(object sender, UploadProgressChangedEventArgs e)
        {
            // a clever way to handle cross-thread calls and avoid the dreaded
            // "Cross-thread operation not valid: Control 'textBox1' accessed 
            // from a thread other than the thread it was created on." exception

            // this will always be called from another thread,
            // no need to check for InvokeRequired

            BeginInvoke(
                new MethodInvoker(() =>
                    {
                        textBox1.Text = (string)e.UserState + "\n\n"
                                        + "Uploaded " + e.BytesSent + "/" + e.TotalBytesToSend
                                        + "b (" + e.ProgressPercentage + "%)";
                    }));
        }

        private void button1_Click(object sender, EventArgs e)
        {
            textBox1.Text = "";

            if (openFileDialog1.ShowDialog() == DialogResult.OK)
            {
                button1.Enabled = false;
                string toUpload = openFileDialog1.FileName;
                textBox1.Text = "Initiating connection";
                new Thread(() =>
                           wcUploader.UploadFileAsync(new Uri("http://anyhub.net/api/upload"), "POST", toUpload)).Start();
            }
        }
    }
}
1 голос
/ 02 мая 2010

В вашем коде есть ошибка, которую стоит исправить, независимо от блокировки пользовательского интерфейса:

Вы указываете два обратных вызова, которые должен запускать асинхронный загрузчик. В этих обратных вызовах вы будете работать в потоке загрузчика; однако вы можете касаться только GUI из основного потока GUI - так что ваши обратные вызовы могут повредить состояние GUI.

Вы не должны касаться textBox1.Text в любом обратном вызове. Маловероятно, что это проблема, но, тем не менее, вы должны исправить это, чтобы избежать сбоев и искажений. Вопрос, который вы связали, иллюстрирует один из способов избежать этого: проверьте свойство Control.InvokeRequired формы (за кулисами это проверяет, находитесь ли вы в нужном потоке) или просто предположите, что требуется вызов, а затем - используйте Control.BeginInvoke вызвать метод в потоке GUI.

Подойдет любой из ваших элементов управления, так как все они работают в одном потоке; так что if (textBox1.InvokeRequired) textBox1.BeginInvoke... так же хорошо, как if (this.InvokeRequired) this.BeginInvoke...

...