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 .
Related
This question already has answers here:
BackgroundWorker RunWorkerCompleted Event
(3 answers)
Closed 3 years ago.
Getting cross thread error trying to update a row in datagridview from Backgroundworker process RunWorkerCompleted event.
I have a separate class where i am doing long running work in the backgroundworker and when complete trying to update the datagridview from the result. Event fires but get a cross tread exception.
Fails when i try to update the gridview here
DataGVhome.Rows[rowIndex].Cells["AlertInfo"].Value = alertMsg.SensorAlert;
From reading numerouos articles and others having issues this is supposed to work i.e. handling the DGV row update once the backgroundworker completed event fires.
private void MSbackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
string hitfile = (string)e.Argument;
e.Result = _MassSpec.ParseMassFile(hitfile);
}
catch(Exception ex)
{
log.Error("Error in MDShomeForm:MSbackgroundWorker_DoWork - " + e.Result.ToString() + " " + ex.Message + Environment.NewLine);
}
}
private void MSbackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// check error, check cancel, then use result
if (e.Error != null)
{
// handle the error
MetroMessageBox.Show(this, "Error in Mass RunWorker = " + e.Error.Message);
}
else if (e.Cancelled)
{
// handle cancellation
MetroMessageBox.Show(this, "Mass RunWorker was Cancelled = " + e.Error.Message);
}
else
{
AlertMsg alertMsg = (AlertMsg)e.Result;
// Test it for hit and update grid in the UI thread
try
{
string searchValue = "";
int rowIndex = -1;
//update the gridview for this sensor
searchValue = alertMsg.SensorType;
foreach (DataGridViewRow row in DataGVhome.Rows)
{
if (row.Cells[2].Value.ToString().Equals(searchValue))
{
rowIndex = row.Index;
break;
}
}
if (rowIndex > -1)
{
// update the L1 Alert for this sensor at rowIndex
DataGVhome.Rows[rowIndex].Cells["AlertInfo"].Value = alertMsg.SensorAlert;
//dataGVhome.Rows[rowIndex].Cells["AlertIndicator"].Value = alertMsg.SensorAlert;
switch (alertMsg.SensorAlertInd)
{
case (int)StandardVals.AlertInds.Green:
DataGVhome.Rows[rowIndex].Cells["AlertIndicator"].Value = "Green";
DataGVhome["AlertIndicator", rowIndex].Style.BackColor = Color.LightGreen;
break;
case (int)StandardVals.AlertInds.Yellow:
DataGVhome.Rows[rowIndex].Cells["AlertIndicator"].Value = "Yellow";
DataGVhome["AlertIndicator", rowIndex].Style.BackColor = Color.Yellow;
break;
case (int)StandardVals.AlertInds.Red:
DataGVhome.Rows[rowIndex].Cells["AlertIndicator"].Value = "Red";
DataGVhome["AlertIndicator", rowIndex].Style.BackColor = Color.Red;
break;
}
DataGVhome.Update();
}
}
catch (Exception ex)
{
log.Error("Error in MDShomeForm:MSBackgroundWorkerCompleted - " + ex.Message + Environment.NewLine);
}
}
// general cleanup code, runs when there was an error or not.
}
I log the exception here
2019-06-26 17:16:18,564 ERROR MDS_Command_Application.MDShomeForm - Error in MDShomeForm:MSBackgroundWorkerCompleted - Cross-thread operation not valid: Control 'DataGVhome' accessed from a thread other than the thread it was created on.
Well this does indeed appear to still be an MS bug (i am using VS 2017 community FYI) since it does not allow updating of the main UI thread form the runworkercompleted event even though the docs say it should.
Below is how i was able to get it to update the UI from this event without the cross thread exception. Wasted a lot of time trying all different ways but hope this helps. Had to use Invoke to bypass this exception.
I had also switched to using a datatable in and fill in hopes it would clear but same issue until i did the below.
Replaced the else code in the RunworkerCompleted event and all good now.
else
{
AlertMsg alertMsg = e.Result as AlertMsg;
// Don't do anything if the form's handle hasn't been created
// or the form has been disposed.
if (!this.IsHandleCreated || this.IsDisposed) return;
// Invoke an anonymous method on the thread of the form.
this.Invoke((MethodInvoker)delegate
{
this.l1SensorAlertsTableAdapter.Fill(aGE_MDS_DevDataSet3.L1SensorAlerts);
});
}
I am currently working on a application that checks emails from an email-account via IMAP. This function is called every 5 seconds and it needs some time to work through.
private void CheckForRequests()
{
List<string[]> mails = CollectAllMails();
for (int i = 0; i < mails.Count; i++)
{
if (mails[i][0].Split('_')[0] == "request")
{
//INVITATION TO ME
if (mails[i][0].Split('_')[2] == username && mails[i][0].Split('_')[3] == "request")
{
DeleteMail(mails[i][0]);
MessageBoxResult result = MessageBox.Show("Do you accept the request from " + mails[i][0].Split('_')[1], "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
if (result == MessageBoxResult.Yes)
{
DeleteMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_" + mails[i][0].Split('_')[3]);
SendMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_accept", "");
ChatWindow chat = new ChatWindow();
chat.ShowDialog();
//do open chat window
}
else if (result == MessageBoxResult.No)
{
DeleteMail("request_" + mails[i][0].Split('_')[1] + mails[i][0].Split('_')[2]);
SendMail("request_" + mails[i][0].Split('_')[1] + "_" + mails[i][0].Split('_')[2] + "_decline", "");
}
}
//ACCEPTION FROM ANOTHER DUDE
else if (mails[i][0].Split('_')[2] != username && mails[i][0].Split('_')[3] == "accept")
{
ChatWindow chat = new ChatWindow();
chat.ShowDialog();
}
//REJECTION FROM ANOTHER DUDE
else if (mails[i][0].Split('_')[2] != username && mails[i][0].Split('_')[3] == "decline")
{
MessageBox.Show("Your invitation was declined.", "Sorry", MessageBoxButton.OK, MessageBoxImage.Exclamation);
}
}
else if (mails[i][0].Split('_')[0] == "somethingelse")
{
}
}
}
My loop calls this method every 5 seconds and in this time I can't write or do anything in my application. Im pretty sure that I have to use a Thread or Task to solve the problem but I didn't found out how to do this related to my case. When I call the method in a Task and I click Yes it crashes and says something like it has to be a STA-Thread... In this case I don't even want to access the GUI with the thread, I just want to check the mails and if the method found something it should do something like break from the Task and call a method (NOT from the Task).
What would be the cleanest solution for this problem?
Your threading issue is caused by you trying to do UI stuff on a non-UI thread. You can solve this problem by using Dispatcher.Invoke whenever you call UI stuff like this
Application.Current.Dispatcher.Invoke(() =>
{
// Your stuff here
});
So in your case you'd have something like this
void CheckForRequests()
{
// Do stuff
Application.Current.Dispatcher.Invoke(() =>
{
// Open your message box
});
// Do more stuff
Application.Current.Dispatcher.Invoke(() =>
{
// Open another message box
});
}
Your are Right about needing to use Threads
While #Gareth is right about the need to use a dispatcher to correctly access elements across theads, i actually don't see any threading in your code, though the error message clearly proves you have attempted some.
to implement the threading you have various options
firstly you can do this directly via Tasks or the older Thread classes
this would simply be done like so
private void CheckForRequestsAsync()=> Task.Run(()=>CheckForRequests());
this will instantly create and start a task that will perform CheckForRequests in a separate thread freeing the GUI to continues its work on the GUI, however this is a very basic implementation and likely will need further enhancement before reliably meeting your needs
another option is to take advantage of some of the newer features in .Net and use the async keyword
if you declare CheckForRequests as private async void CheckForRequests (object sender, EventArgs e) then the void will be automatically be converted into a task which can be fired off by an event hander as a async task say of a Timer
eg
Timer timer = new Timer(5000);
timer.Elapsed += CheckForRequests; //where CheckForRequests has the async keyword
timer.Start();
combine this with the dispatcher information #Gareth suggested on anything that throws a cross thread access exception and you should be ready
this would look something like this:
MessageBoxResult result = Dispatcher.Invoke(() =>
MessageBox.Show("Do you accept the request from " + mails[i][0].Split('_')[1], "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes);
);
Note if you use async with out using the await keyword then you will get warnings that your thread may exit before any worker threads have completed, however this is just a warning as if you aren't calling any worker threads inside your method or you don't need them to complete before exiting then there is no harm done
Finally
one of the comments suggested using DispatcherTimer rather than a Timer, i would not suggest this as every time the timer ticks, it will run your code in the GUI thread locking it just as you are already seen, the DispatcherTimer is best used when the timer heavily changes the GUI and is quick running
though if you redefined your code then you could user a DispatcherTimer by breaking out the gui element from the slow running process
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += (s,e)=>{
if( MessageBox.Show("Do you accept the request", "Invitation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes) == MessageBoxResult.Yes)
{
Task.Run(()=>CheckForRequests());
}
}
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();
Sorry for the inappropriate title. I have program as below
int status = 100;
richTextBox1.AppendText("Starting Process....." + "\n");
status = comm.WriteData(cmd1);
if (status == 0)
{
richTextBox1.AppendText("COMMAND 1 OK" + "\n");
}
else
{
richTextBox1.AppendText("COMMAND 1 NOT OK" + "\n");
}
richTextBox1.AppendText("Gathering signal from product sensor....." + "\n");
status = comm.WriteData(cmd2);
if (status == 0)
{
richTextBox1.AppendText("COMMAND 2 OK" + "\n");
}
else
{
richTextBox1.AppendText("COMMAND 2 NOT OK" + "\n");
}
richTextBox1.AppendText("Measuring current value" + "\n");
status = comm.WriteData(cmd3);
if (status == 0)
{
richTextBox1.AppendText("COMMAND 3 OK" + "\n");
}
else
{
richTextBox1.AppendText("COMMAND 3 NOT OK" + "\n");
}
This will execute on a button click event. Like this I need to send 130 commands. The overall process takes almost 6 minutes. The code works fine. But until the execution of all commands (for 6 minutes), the RichTextBox appears to be empty. After execution of each command I need to display message to operator whether the command is OK or NOT OK. I even tried by giving delay between each command. but can't achieve what I need. Please support to resolve this.
I'd use a BackgroundWorker here, and use ReportProgress to add to the textbox. Something like (off my mind):
public void ButtonClick()
{
// Since your code will now be async, you should need to handle reentrance here
var worker = new BackgroundWorker();
worker.ReportsProgress = true;
worker.ProgressChanged += ProgressLog;
worker.DoWork += TestComm;
worker.RunWorkerAsync();
}
private void ProgressLog(object sender, ProgressChangedEventArgs e)
{
richTextBox1.AppendText(e.UserState.ToString() + Environment.NewLine);
}
private void TestComm(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
worker.ReportProgress(0, "Starting Process.....");
// Sub-optimal way to construct a string :-)
worker.ReportProgress(0, "COMMAND 1 " + (comm.WriteData(cmd1) == 0 ? "OK" : "NOT OK"));
worker.ReportProgress(0, "Gathering signal from product sensor.....");
worker.ReportProgress(0, "COMMAND 2 " + (comm.WriteData(cmd2) == 0 ? "OK" : "NOT OK"));
worker.ReportProgress(0, "Measuring current value");
worker.ReportProgress(0, "COMMAND 3 " + (comm.WriteData(cmd3) == 0 ? "OK" : "NOT OK"));
// etc.
}
As a side note, if all you do is "show a header string", "run command", write "command # ok/not ok", this could be easily refactored into a loop, but I'll leave that to you :-)
Also, since your code would now be asynchronous, you'd need to handle reentrance to the button's Click event, since a user could now click the button while the test is running (and thus would run two tests simultaneously, which, if you are using comm ports, might not be possible).
This could be as simple as disabling the button when you create the worker, and enabling it again on the BackgroundWorker.RunWorkerCompleted event, or you could complicate it as much as you want (cancelling the initial test and starting it again, or whatnot).
As stated on the comments, you also need help with the reentrance. The simplest way would be something like:
public void ButtonClick()
{
// Disable the button
myButton.Enabled = false;
var worker = new BackgroundWorker();
worker.ReportsProgress = true;
worker.ProgressChanged += ProgressLog;
worker.DoWork += TestComm;
// Re-enable the button when the worker has completed (or failed)
worker.RunWorkerCompleted += (o,e) => myButton.Enabled = true;
worker.RunWorkerAsync();
}
You need to write your data in a separate thread. What's happening is your UI thread is waiting for execution of your function to finish before resuming normal operations like updating controls. You can read more in the documentation for winforms's BackgroundWorker Class.
try this code after each AppendText:
richTextBox1.Refresh();
I've spend now over 2 hours trying to resolve this issue and it would be awesome if someone could help me .. :)
Basically, what I try to to is an application which queries a database with LINQ and webservices and retrieves informationen.
From the result I extract few informations and write them to a .CSV file.
Everything works perfectly, except the logging.
Since I dont want that my UI frezzes, I've implemented a background worker:
I hand my logger textbox over to the background worker, that I write the progress from my static webservice methods.
using TextBoxAsAlias = System.Windows.Controls.TextBox;
Here I write for the first time to the logger textbox, which works absolutely perfect.
private void btnExecute_Click_1(object sender, RoutedEventArgs e)
{
// Register background worker
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
// Flush the content of the logger textbox
txtboxLogger.Clear();
txtboxLogger.AppendText("#########################" + Environment.NewLine);
txtboxLogger.AppendText("# Logger - " + DateTime.Now.ToString("T") + "#" +
txtboxLogger.AppendText("#########################" + Environment.NewLine + Environment.NewLine);
worker.RunWorkerAsync(new Object[] {txtboxLogger });
}
Here is where I get the problem:
As you can see I try again to log some text in the DoWork method.
The problem is, that the whole text will just be logged when the worker_DoWork has finished.
So as result I wait for 5minutes and nothing happens in the logger textbox and as soon as it finished, everything gets written at once.
void worker_DoWork(object sender, DoWorkEventArgs e)
{
Object[] parameter = (Object[])e.Argument;
TextBoxAsAlias textboxLogger = (TextBoxAsAlias)parameter[0];
textboxLogger.Dispatcher.InvokeAsync((Action)(() =>
{
txtboxLogger.AppendText(DateTime.Now.ToString("T") + " - Start processing ... " + Environment.NewLine);
if (isAutoSelection)
{
// Execute Webservice with auto selection
RandomDoWorkMethod(null, context, credential, dateStart, textboxLogger);
}
else
{
// Read în the Numbers + hand it over to the webservice for further computing
RandomDoWorkMethod(ReadInputFile(), context, credential, dateStart, textboxLogger);
}
}));
}
Does anyone know how I can write immediately during the background Worker to the log file and not just at the end?
I make further use of the following code in my other methods, but the result is still the same, because all of them are in the worker_DoWork thread.
textboxLogger.Dispatcher.InvokeAsync((Action)(() =>
{
))};
Thanks in advance for your help.
Regards
George
Use SynchronizationContext of your UI thread.
Global variable:
private System.Threading.SynchronizationContext _uiSyncContext;
In constructor:
this._uiSyncContext = System.Threading.SynchronizationContext.Current;
Working with UI elements in worker_DoWork method:
this._uiSyncContext.Post(
delegate(object state)
{
txtboxLogger.AppendText(state as string);
},
"Your Text"
);
You can also use Send (Synchronous) method instead of Post (Asynchronous).
Using the dispatcher like that pushes all the work back to the UI, you should only use the dispatcher to refresh the text every now and then, never push RandomDoWorkMethod using the dispatcher.
Also consider using binding (also see notes on BackgroundWorker and ProgressChanged).
This sounds like a standard scenario for using the ProgressChanged event.
A BackgroundWorker thread cannot directly communicate with the UI thread, but it does expose an event that can. Here is how you set this up:
In your "Register background worker" section, add the following lines:
worker.WorkerReportsProgress = true;
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
You need to define the handler somewhere inside your class to look something like this:
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0)
{
string message = DateTime.Now.ToString("T") + " - Start processing ... ";
txtboxLogger.AppendText(message + Environment.NewLine);
}
}
Notice, we're assuming a progress of 0 means we've just started our process. This value needs to be sent inside the DoWork event handler. So your DoWork method will now look like this:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
worker.ReportProgress(0);
if (isAutoSelection)
{
// Execute Webservice with auto selection
RandomDoWorkMethod(null, context, credential, dateStart, textboxLogger);
}
else
{
// Read în the Numbers + hand it over to the webservice for further computing
RandomDoWorkMethod(ReadInputFile(), context, credential, dateStart, textboxLogger);
}
}
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...