UploadFileAsync not asynchronous? - c#

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

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

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...

Related

Calling Process.WaitForExit() in button clicked event is blocking program

I have written same app using Windows Forms to run process after button was pushed and read its standard output. When I call method "test()" in button1_Click() my program is blocking. But when I call "test()" in "Form1" constructor everything work as expected. Where is problem?
using System;
using System.Windows.Forms;
namespace DISMassistant
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
process1.StartInfo.RedirectStandardError = true;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "/?";
}
public void button1_Click(object sender, EventArgs e)
{
}
public void test()
{
process1.Start();
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.WaitForExit();
process1.CancelOutputRead();
process1.CancelErrorRead();
process1.Close();
}
private void process1_OutputDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
private void process1_ErrorDataReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
}
}
Fixing all of the missing parts in your sample code, there's no issue (beyond, of course, that you wouldn't want to WaitForExit in a GUI application):
// Warning - The code below is WRONG! Awful even, since it will _appear_ to work in some cases.
void Main()
{
Application.Run(new Form1());
}
public class Form1 : Form
{
Process process1 = new Process();
Button button1;
RichTextBox richTextBox1;
public Form1()
{
button1 = new Button { Text = "Run" };
button1.Click += button1_Click;
Controls.Add(button1);
richTextBox1 = new RichTextBox { Left = 100 };
Controls.Add(richTextBox1);
process1.StartInfo.RedirectStandardError = true;
process1.StartInfo.RedirectStandardOutput = true;
process1.StartInfo.UseShellExecute = false;
process1.StartInfo.FileName = "cmd.exe";
process1.StartInfo.Arguments = "/?";
}
public void button1_Click(object sender, EventArgs e)
{
test();
}
public void test()
{
process1.Start();
process1.OutputDataReceived += process1_OutputDataReceived;
process1.ErrorDataReceived += process1_ErrorDataReceived;
process1.BeginOutputReadLine();
process1.BeginErrorReadLine();
process1.WaitForExit();
process1.CancelOutputRead();
process1.CancelErrorRead();
process1.Close();
}
private void process1_OutputDataReceived(object sender,
System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
private void process1_ErrorDataReceived(object sender,
System.Diagnostics.DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data)) return;
richTextBox1.Text += e.Data + "\n";
}
}
However, this is still something you do not want to do. GUI controls shouldn't be accessed from a different thread than they were created on. I'm not getting a problem in my test application, but even if it works, this is just disaster waiting to happen.
It so happens that RichTextBox has a bit weird handling of the Text property. If the handle isn't created yet, Text only changes a single field in the class. It doesn't do any multi-threaded access checking or anything. Now, when the handle actually gets created, the value from this field is applied to the text of the control for real - on the correct thread.
This is probably why you're seeing your weird behaviour. In the form constructor, the handle isn't created yet. The access from a background thread is still a bad idea, but it doesn't actually break anything, most of the time (you may lose some of the process output). When the form is shown, the handles are created, and the text box shows the "correct" output.
When the form is already created (and shown), this changes. On a single-threaded apartment thread, you're expected to handle multi-threading yourself. You're not supposed to access a control from a different thread - and most controls will call you out on it and throw an exception. For whatever reason, RichTextBox isn't (always) that kind of control. So as it happens, on STA, the code happens to work - not reliably or safely, but it doesn't hang, and it doesn't crash.
But your UI thread seems to be in a multi-threaded apartment. And in MTA, when you try to assign text to the RichTextBox (with a created handle), the message is marshalled to the UI thread. But your UI thread is busy waiting for the process to exit! Thus, you hang.
How do you solve this?
Never access GUI controls from a different thread than the one they were created on. Marshal the call explicitly (e.g. using Invoke), and you'll get reliable and consistent behaviour.
Do not block the UI thread. Process has an Exited even that allows you to react to the process exiting in an asynchronous way.

Backgroundworker with FileSystemWatcher

I'm trying to implement a BackgroundWorker for monitoring a FileSystemWatcher service.
My code is divided as:
A Classes.cs wich contains all methods , variables and FileSystemWatcher implementation. And the main Form1 , wich contains form data and calls for the buttons\etc. When i run my program all that happens is the cursor to change (this was already expected) - the action happens in the background (things get done) but no report is shown on my progress bar. I got the example from a website and adapted it to my code - is there anything wrong i'm doing ? I believe there's something involved with the fact the only thing i call is the filesystemwatcher - but i expected that it would report the progress based on the action running "on background".
Any help is appreciated. Thanks
My form1 code (the BackgroundWorker part) and the FileSystemWatcher follows:
namespace PPF_Converter_v10
{
public partial class Form1 : Form
{
private FileManipulation prg;
//private FileManipulation FileOp;
public Form1()
{
InitializeComponent();
//FileOp = new FileManipulation();
prg = new FileManipulation();
//Load config before the program begins - loading sample config or newly generated config
prg.LoadConfig();
FillTextBox();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
}
BackgroundWorker CODE:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (!textBox1.Text.Contains("\\"))
{
MessageBox.Show("Please define the input folder before starting");
}
else if (!textBox2.Text.Contains("\\"))
{
MessageBox.Show("Please define the XML Output folder before starting");
}
else if (!textBox3.Text.Contains("\\"))
{
MessageBox.Show("Please define the Converted PPF Output Folder before starting");
}
else if (!textBox4.Text.Contains("\\"))
{
MessageBox.Show("Please define the Invalid PPF Output Folder before starting");
}
else
{
// calls the watcher
// prg.FileWatcher.SynchronizingObject = progressBar1.
prg.ProgramProcessing(textBox1.Text);
}
// do some long-winded process here
// this is executed in a separate thread
int maxOps = 1000000;
for (int i = 0; i < maxOps; i++)
{
//rtbText.AppendText(i.ToString() + "\r\n");
// report progress as a percentage complete
bgWorker.WorkerReportsProgress = true;
bgWorker.ReportProgress(100 * i / maxOps);
}
}
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// update the progress bar
pbProgress.Value = e.ProgressPercentage;
}
private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// return to "normal" mode of operation
this.Cursor = Cursors.Default;
btnGo.Enabled = true;
}
private void btnGo_Click_1(object sender, EventArgs e)
{
// give the appearance of something happening
this.Cursor = Cursors.WaitCursor;
btnGo.Enabled = false;
// call RunWorkerAsync to start the background thread
bgWorker.RunWorkerAsync();
}
Exception thrown when the RichtextBox is enabled:
Additional information: Cross-thread operation not valid: Control 'rtbText' accessed from a thread other than the thread it was created on.
You're invoking a MessageBox from the background thread on the foreground thread. That is like doing UI in two separate threads which is a no-no.
What you can do is either use events or an event aggregator from your background thread. I would probably go for the latter. This way, your background thread, when something is wrong, can (and should) abort immediately and notify through a message that it was unable to process the file.
Think of a background task as something that has no UI whatsoever. It is there in the background and can only communicate to the UI thread using events or messages.

Background Worker is Running but Doesn't Print anything

I have a BackgroundWorker called bgw to whom I pass my custom Form called LogBoxForm to. The custom Form job is simply to print something on it.
LogBoxForm logBox = new LogBoxForm(); //both declared in the main Form
BackgroundWorker bgw = new BackgroundWorker();
In the main Form's Load event initiated the two bgw events: DoWork and RunWorkerCompleted like this
bgw.DoWork += bgw_DoWork;
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
And then when I pressed a Button named button9, the bgw will be ran as directed by this code
//Background worker
BackgroundWorker bgw = new BackgroundWorker();
private void button9_Click(object sender, EventArgs e) {
if (bgw.IsBusy)
return;
bgw.RunWorkerAsync(logBox);
}
void bgw_DoWork(object sender, DoWorkEventArgs e) {
LogBoxForm lbf = e.Argument as LogBoxForm;
try {
for (int i = 0; i < 5; ++i) {
lbf.WriteTimedLogLine("loop " + (i + 1).ToString());
Thread.Sleep(1000);
}
} catch (Exception exc) {
throw exc;
}
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
logBox.WriteTimedLogLine("Completed!");
if (e.Error != null)
logBox.WriteTimedLogLine(e.Error.ToString());
}
It stops in the catch line This is the error message I get:
System.InvalidOperationException: Cross-thread operation not valid:
Control 'richTextBoxAll' accessed from a thread other than the thread
it was created on.
I am pretty new user for BackgroundWorker and may not really be aware how all these can happen. I am hoping that some more experienced can tell what is wrong with my code. I am looking forward for your guidance.
This should be a classic case of Thread Affinity. Since the BackgroundWorker runs on a different thread other than the UI thread you need to call .Invoke. Checkout this link here to see an example of an extension method that encapsulates thread-safe invocation.
With this extension method you can write WinForms thread-safe code like this:
this.ThreadSafeInvoke(() => logBox.WriteTimedLogLine("loop " + (i + 1).ToString()));
The key here is that since your execution on a different thread, the .InvokeRequired bool will return true and then you'll execute the Action passed into the ThreadSafeInvoke via the .Invoke -- which will marshal back to the UI thread.
If you do not want to have an extension method simply do the following instead:
this.Invoke(new MethodInvoker(() =>
logBox.WriteTimedLogLine("loop " + (i + 1).ToString())));
The advantages to the extension method are obvious. I hope you find this helpful.

About Winform threading in UI C#

When I use a delegate method to run my method in order to let the UI refresh, the UI does not refresh. I want stop the UI from freezing while the method runs, what can I do?
private void SearchToolStripButton_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(StartMethod));
t.Start();
}
private delegate void InvokeDelegate();
public void StartMethod()
{
this.BeginInvoke(new InvokeDelegate(SearchData));
}
public void SearchData()
{
if (searchKeywordTextBox.Text == "")
{
MessageBox.Show("Please type the keyword!");
}
else
{
if (searchDateTimePicker.Checked == true)
{
switch (selectRangeComboBox.Text)
{
case "Day": showDataToresultDataGridViewOnDay(searchKeywordTextBox.Text);
break;
case "Month": showDataToresultDataGridViewInMonth(searchKeywordTextBox.Text);
break;
case "Year": showDataToresultDataGridViewInYear(searchKeywordTextBox.Text);
break;
default: MessageBox.Show("Please select a Section");
break;
}
}
else
{
showDataToresultDataGridView(searchKeywordTextBox.Text);
}
}
}
public void showDataToresultDataGridViewOnDay(string keyword)
{
DataGridView dayGrid = resultDataGridView;
ShowResultDay day = new ShowResultDay();
resultDataGridView.DataSource = day.ShowGridDay(searchDateTimePicker.Value.Day, searchDateTimePicker.Value.Month, searchDateTimePicker.Value.Year, keyword);
resultLabel.Text = "Showing " + resultDataGridView.RowCount + " records in " + searchDateTimePicker.Value.Day + "/" + searchDateTimePicker.Value.Month + "/" + searchDateTimePicker.Value.Year;
}
public void showDataToresultDataGridViewInMonth(string keyword)
{
DataGridView monthGrid = resultDataGridView;
ShowResultMonth month = new ShowResultMonth();
resultDataGridView.DataSource = month.ShowGridMonth(searchDateTimePicker.Value.Month, searchDateTimePicker.Value.Year, keyword);
resultLabel.Text = "Showing " + resultDataGridView.RowCount + " records in " + searchDateTimePicker.Value.Month + "/" + searchDateTimePicker.Value.Year;
}
public void showDataToresultDataGridViewInYear(string keyword)
{
DataGridView yearGrid = resultDataGridView;
ShowResultYear year = new ShowResultYear();
resultDataGridView.DataSource = year.ShowGridYear(searchDateTimePicker.Value.Year, keyword);
resultLabel.Text = "Showing " + resultDataGridView.RowCount + " records in " + searchDateTimePicker.Value.Year;
}
public void showDataToresultDataGridView(string keyword)
{
ShowAllData all = new ShowAllData();
var results = all.ShowGirdAll(keyword);
resultDataGridView.DataSource = results;
resultLabel.Text = "Showing " + resultDataGridView.RowCount + " records";
}
You should have a look at BackgroundWorker class, that has been implemented expressly to make multi-threading easier in WinForms.
Then I'd suggest another thing:
don't open the messageboxes from the the background thread, but check properties and fields before to start the thread, and then run only the search in the non-ui thread.
Example of background worker usage:
// Initialization
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
// Start elaboration
bw.RunWorkerAsync(objectArgument);
void bw_DoWork(object sender, DoWorkEventArgs e)
{
// do your work (we are in the background-thread)
// when you have finished, set your results in the e.Result property
// N.B. don't show anything because we are in the background-thread
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// here we have finished the work (we are in the UI-thread)
// result is set in e.Result property
// N.B. check e.Error property before to get e.Result because
// if there's an error e.Result throws an exception
}
Unlike Native Win32 API or MFC , Dot-net framework doesn't support multithreaded UI method's *calls for control's . Thus you can't create UI control (window) in any thread and call it's method and set property from another one !
(*here and below - UI method's that updates (refreshes) UI context . You can read data from control and can't write to)
By the way , in the native environment such calls don't work reliably too, sometimes throwing badly managed exception ...
But managed environment can not allow the method call would cause an exception at all ! It just 'freezes' a such call and do nothing to refresh .
Incidentally, this feature of the platform is not only for background threads . Whether calling BeginInvoke (from the built-in thread pool), or creating a user's non-background thread by another way you'll get the same problem .
In your specific case I cannot see any reason to have two or more threads share the same control - it is also dangerous in terms of data safety . Typically, other threads (and generally parallelism) are used for entirely different purposes: for any long time computing , I/O operations of databases , files, network.
Although, of course, you can invoke some Message Boxes in other thread .

Threading Basics

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace testThreads
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
textBox1.Text = "Counting to 10000000, value is " + i + Environment.NewLine;
}
}
public void countToZero()
{
for (int i = 10000000; i > 0; i--)
{
textBox2.Text = "Counting to 0, value is " + i + Environment.NewLine;
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread countUp = new Thread(new ThreadStart(countToLots));
Thread countDown = new Thread(new ThreadStart(countToZero));
countUp.Start();
countDown.Start();
}
private void button2_Click(object sender, EventArgs e)
{
textBox3.Text = "Bobby bob bob " + Environment.NewLine;
}
}
}
I really need to try and get the hang of this - i just dont understand the theory behind why i get an error message. Could someone help me out please?
Cross-thread operation not valid:
Control 'textBox1' accessed from a
thread other than the thread it was
created on.
UI controls have "thread affinity"; they do not want to be touched by anything except the UI thread; that includes reading and writing properties. The assignment to .Text should be done from the UI thread, either by using Invoke, or BackgroundWorker.
For example:
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
// running on bg thread
textBox1.Invoke((MethodInvoker) delegate {
// running on UI thread
textBox1.Text = "Counting to 10000000, value is "
+ i + Environment.NewLine;
});
// running on bg thread again
}
}
But note that this type of thread switching has overhead. You should not call back every iteration - you should (for example) update the UI every [n] iterations - in the above, every 10000 for example.
You cannot use a method or property of a Form control from a different thread than the thread that created (called new) the control.
To do that just do:
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
SetText("Counting to 10000000, value is " + i + Environment.NewLine);
}
}
public void SetText(string text)
{
if (this.textBox1.InvokeRequired())
{
Action<string> auxDelegate = SetText;
this.BeginInvoke(auxDelegate,text);
}
else
{
this.textBox1.Text = text;
}
}
What the method is doing with the beginInvoke is just calling again the SetText method from the thread that created the control.
Ok, about the theory behind WHY controls have UI Thread affinity.
If you've programmed long enough you would remember the days when forms and rapid application development were not the standard. In those days just droping a control into a form was rare... everything was done by the old school.
Now, in windows, the "old school" way of doing things involved defining a WindowProc.
The WindowProc is a function which is invoked to handle application message (notice I say is, not was). This function runs on the main program thread and is in charge of handling every message the application receives, including user interface paint and refresh.
Nowadays all that is mostly automated so that when you create a form the code in charge of doing all the work is autogenerated and you don't have to worry about that... but it is still there.
Of course, if the thread in charge of drawing the user interface with all its controls, is the main thread, you'll see how changing things from another thread might disturb the application itself with race conditions and so on. In addition, since the UI handling is autogenerated you can't just put the synchronization mechanisms that you'll use with two standard threads because you only have access to code on one thread, yours, but not to the main windowproc callback.
In a way, what BeginInvoke will do for you is pass a message to the main thread telling it to kindly handle the delegate in her own context when the time is right, thus delegating the execution to the main thread.

Categories

Resources