问题
Aight, did a bit of Googling and searching here, the only question I found related was this, although the only answer it had wasn't marked as accepted, is old and is confusing.
My problem is basically what I've said in the title. What happens is that the GUI freezes while the upload is in progress. My code:
// 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 + "%)";
}
}
EDIT: For clarification, this is what happens in order:
- I click button1
- I select a file
- The GUI stops responding, as in when I click on it nothing happens
- After a couple of seconds 50% shows up in the textbox Aaand the realisation hits. See my comment to the question I marked as the solution
- After a second or so with the GUI not responding in-between it's replaced with the response
回答1:
Sure it is.
The code works just fine.
wcUploader.UploadFileAsync(...)
initiates the request and execution continues, meanwhile the progress is updated in TextBox1 and upon completion I get some JSON.
That is Async. If you simply called wcUploader.UploadFile
, execution would block there until the file was uploaded and you would get no progress events.
Bottom line:
The UI is not blocked, progress events are called and UI is updated in real time.
Update:
To eliminate the initial block when the webclient is establishing the http connection, simply call the upload on another thread. In this scenario, you must use invocation to prevent cross thread exceptions:
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();
}
}
}
}
回答2:
There is a bug in your code that's worth fixing anyhow, regardless of the UI locking:
You specify two callbacks which the asynchronous uploader should trigger. In those callbacks, you'll be running on the uploader's thread; however, you may only touch the GUI from the main GUI thread - so your callbacks might corrupt the GUI's state.
You shouldn't touch textBox1.Text
in either callback. It's unlikely that's the problem, but nevertheless, you should fix it to avoid crash and corruption bugs. The question you've linked illustrates one way of avoiding this: check the form's Control.InvokeRequired
property (behind the scenes this checks whether you're on the right thread), or simply assume an invoke is required and then - use Control.BeginInvoke
to trigger a method on the GUI thread.
Any of your controls will do since they all run in the same thread; so if (textBox1.InvokeRequired) textBox1.BeginInvoke...
is just as good as if (this.InvokeRequired) this.BeginInvoke...
来源:https://stackoverflow.com/questions/2753071/uploadfileasync-not-asynchronous