【问题标题】:UploadFileAsync not asynchronous?UploadFileAsync 不是异步的?
【发布时间】:2010-05-02 10:01:53
【问题描述】:

好吧,在这里做了一些谷歌搜索和搜索,我发现唯一相关的问题是this,尽管它的唯一答案没有被标记为已接受,是旧的并且令人困惑。

我的问题基本上就是我在标题中所说的。发生的情况是 GUI 在上传过程中冻结。我的代码:

// 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. GUI 停止响应,因为当我单击它时没有任何反应
  4. 几秒钟后 50% 出现在文本框中 Aa 并且实现命中。查看我对我标记为解决方案的问题的评论
  5. 大约一秒钟后,GUI 中间没有响应,它被替换为响应

【问题讨论】:

  • 为什么你认为它不是异步的?

标签: c# webclient


【解决方案1】:

确实如此。

代码运行良好。

wcUploader.UploadFileAsync(...) 发起请求并继续执行,同时在 TextBox1 中更新进度,完成后我得到一些 JSON。

那是异步的。如果您只是调用wcUploader.UploadFile,则执行将在那里阻塞,直到文件上传并且您不会收到任何进度事件。

底线:

UI不被阻塞,进度事件被调用,UI实时更新。

更新:

要在 webclient 建立 http 连接时消除初始阻塞,只需在另一个线程上调用 upload。在这种情况下,您必须使用调用来防止跨线程异常:

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

【讨论】:

  • 哦。哦。哦。我认为它不是异步的原因是因为我选择上传的文件太小了,基本上没有机会......但是有,而且我很快就把这种进展的外观作为一个错误排除在外。 GUI确实冻结,但在上传开始和结束之间只有几秒钟。
  • 您可以通过在另一个线程中调用wcUploader.UploadFileAsync 来摆脱初始暂停。我会将其添加到我的答案中,但是 - 然后您必须使用 eamon 提到的调用模式。
【解决方案2】:

无论 UI 锁定如何,您的代码中都有一个值得修复的错误:

您指定异步上传器应触发的两个回调。在这些回调中,您将在上传者的线程上运行;但是,您只能从 GUI 主线程触摸 GUI - 因此您的回调可能会破坏 GUI 的状态。

您不应该在任何回调中触摸textBox1.Text。这不太可能是问题,但是,您应该修复它以避免崩溃和损坏错误。您链接的问题说明了避免这种情况的一种方法:检查表单的 Control.InvokeRequired 属性(在幕后检查您是否在正确的线程上),或者只是假设需要调用然后 - 使用 Control.BeginInvoke触发 GUI 线程上的方法。

您的任何控件都可以,因为它们都在同一个线程中运行;所以if (textBox1.InvokeRequired) textBox1.BeginInvoke...if (this.InvokeRequired) this.BeginInvoke... 一样好

【讨论】:

  • 发布的原始代码看起来好像需要调用,但实际上不需要。但是,我发布的更新代码确实如此。此外,您只应在不确定调用来源时检查 InvokeRequired。在给出的示例中,调用总是来自同一个地方,因此不需要检查。
  • 为什么原始代码是安全的? webclient 回调是否在原始线程上执行?记录在哪里?
  • 我不按要求回答您的问题,而是问您几个问题:您是否尝试运行代码?您是否使用 WebClient 的异步调用编写过代码?我猜答案是否定的。您是否对 WebClient 类的行为做出了假设?我必须假设答案是肯定的。我会把剩下的留作练习。干杯..
  • 相反:我不假设异步回调必须在原始线程上执行。你居高临下的语气表明对特定行为感到好奇是一件奇怪的事情。我了解到“它可以在我的机器上运行”并不能保证它可以在各种版本和不同的上下文中普遍工作。我很愿意相信你说的话,但这并没有让我对知道这是我可以依赖的特定行为还是仅仅是在未来版本或 Windows 窗体以外的上下文中可能不起作用的意外事件不感兴趣.
猜你喜欢
  • 2015-01-12
  • 2013-03-26
  • 1970-01-01
  • 2019-11-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多