I have a method that is meant to display output on a RichTextBox in a form.
public void LogTextEvent(RichTextBox TextEventLog, Color TextColor, string EventText)
{
string nDateTime = DateTime.Now.ToString("hh:mm:ss tt") + " - ";
// color text.
TextEventLog.SelectionStart = TextEventLog.Text.Length;
TextEventLog.SelectionColor = TextColor;
// newline if first line, append if else.
if (TextEventLog.Lines.Length == 0)
{
TextEventLog.AppendText(nDateTime + EventText);
TextEventLog.ScrollToCaret();
TextEventLog.AppendText(System.Environment.NewLine);
}
else
{
TextEventLog.AppendText(nDateTime + EventText + System.Environment.NewLine);
TextEventLog.ScrollToCaret();
}
}
The problem arises when I call LogEventText() from another method running inside a thread:
Thread thListening = new Thread(new ThreadStart(StartListening));
thListening.Start();
Inside the StartListening method (it is a thread created that handles new tcp sockets to be created for clients on a main listening socket for a small http server), I make use of LogTextEvent to log some data, but I receive an InvalidOperationException was unhandled error in the LogTextEvent method at the 2nd line, TextEventLog.SelectionStart = TextEventLog.Text.Length;
The error reads, Cross-thread operation not valid: Control 'rchEventLog' accessed from a thread other than the thread it was created on.
Can anyone help break down what's going on, why, and how the error could be fixed?
Try something like this:
public void LogTextEvent(RichTextBox TextEventLog, Color TextColor, string EventText)
{
if (TextEventLog.InvokeRequired)
{
TextEventLog.BeginInvoke(new Action(delegate {
LogTextEvent(TextEventLog, TextColor, EventText);
}));
return;
}
string nDateTime = DateTime.Now.ToString("hh:mm:ss tt") + " - ";
// color text.
TextEventLog.SelectionStart = TextEventLog.Text.Length;
TextEventLog.SelectionColor = TextColor;
// newline if first line, append if else.
if (TextEventLog.Lines.Length == 0)
{
TextEventLog.AppendText(nDateTime + EventText);
TextEventLog.ScrollToCaret();
TextEventLog.AppendText(System.Environment.NewLine);
}
else
{
TextEventLog.AppendText(nDateTime + EventText + System.Environment.NewLine);
TextEventLog.ScrollToCaret();
}
}
If the LogTextEvent method is called from a thread other than the rich text box's UI thread, it will post a message to the UI thread which will cause the LogTextEvent method to be called in that thread.
By using BeginInvoke instead of Invoke, it is posted asynchronously which means that it returns immediately to the caller instead of waiting for the method to be executed on the other thread. For a logging scenario like this, this is probably what you want. If you needed to get a value from a control or something you would probably need to use Invoke instead.
new Thread(new ThreadStart(delegate {
//do some worker-thread processing here
...
//now update the gui from within the gui thread
MyRichTextBox.Invoke(new Action(delegate {
MyRichTextBox.SelectionStart = CalculateTheSelectionStart();
}));
})).Start();
Related
I am trying to run a series of tasks in the background of my Telnet / SSH program. sadly it seems I am not able to find out how to get this to work on the background.
I have tried using Tasks :
Task ReindexStock = new Task(delegate
{
this.Invoke(new Action(() =>
{
btnReindexStock.PerformClick();
txtBoxInput.Text = command[1];
ExecuteCommand();
}
));
});
ReindexStock.Start();
ReindexStock.Wait();
Task Product_attribute = new Task(delegate
{
this.Invoke(new Action(() =>
{
btnReindexProduct_Attribute.PerformClick();
txtBoxInput.Text = command[2];
ExecuteCommand();
}
));
});
Product_attribute.Start();
Product_attribute.Wait();
I also tried threads :
new Thread(() =>
{
btnReindexStock.PerformClick();
txtBoxInput.Text = command[1];
ExecuteCommand();
}).Start();
new Thread(() =>
{
btnReindexProduct_Attribute.PerformClick();
txtBoxInput.Text = command[2];
ExecuteCommand();
}).Start();
as well as this ( plucked this one from the net, hoped it would work ):
ThreadPool.QueueUserWorkItem(delegate
{
btnReindexStock.PerformClick();
txtBoxInput.Text = command[1];
ExecuteCommand();
});
ThreadPool.QueueUserWorkItem(delegate
{
btnReindexProduct_Attribute.PerformClick();
txtBoxInput.Text = command[2];
ExecuteCommand();
});
But for some reason my program still freezes when it's doing ExecuteCommand (
var cmd = SSH.client.CreateCommand(txtBoxInput.Text);
var result = cmd.Execute();
this.Invoke(new Action(() =>
{
rTxtBoxOutput.Text += result;
var reader = new StreamReader(cmd.ExtendedOutputStream);
rTxtBoxOutput.Text += "\n" + reader.ReadToEnd();
}
));
)
I have tried several things in the backgroundworker_doWork aswell but none of them seemed to work.. I tried to start a new thread like this
new Thread(new ThreadStart(ReindexAll)).Start();
also like this but I guess this is practically the same but larger
Thread t = new Thread(new ThreadStart(ReindexAll));
t.Start();
t.IsBackground = true;
and
Task.Factory.StartNew(() => ReindexAll());
as well as the plain and simple
ReindexAll();
but as I said before none of it seems to work, the moment I execute my command the program freezes.
Now my question is if someone is able to tell me what I am doing wrong and hopefully help me
From most of your examples, it looks like you're trying to click a button on your background thread to start performing whatever task this is.
all UI is only able to work on the UI thread. Most of this would almost definitely cause an error, I would think. If you're trying to do something strictly off of the UI thread, you can. However, you shouldn't be triggering a button on another thread to do so. Typically speaking, the button should be triggered by your operator only.
If you need to do some work without the operator's involvement, you can either do it in the start of the app when everything is loading up, or you can set a timer and trigger the work via that. Again, this shouldn't trigger a button, but the work itself.
Okay, first up - I'd suggest simplifying the stuff where you're creating Tasks. Instead of trying to write an inline delegate, just break out the Task's code into a separate function, and then have the Task refer to it. Much simpler, much cleaner.
Task myTask = new Task(TaskCode);
// elsewhere ...
private void TaskCode()
{
// stuff
}
At that point, after creating myTask, you can simply call myTask.Start() and you're done.
Now, that's only one half of your problem. If you tried this:
private void TaskCode()
{
SomeGuiControl.Text = "something";
}
... you're going to get an error that you can't update a GUI control from the non-gui thread. However, you can use Invoke or BeginInvoke to kick over a message to the GUI thread to handle an update (see: Writing to a TextBox from another thread?) The simplest being something like:
txtResults.BeginInvoke(new Action(() => txtResults.Text = DateTime.Now.ToString() + " Hello World"));
Finally, there's one additional caveat that's very important. You shouldn't do a Task.Wait() on the GUI thread. Why? Because all the GUI events won't fire off until the Task.Wait() finishes - which means, if you're doing updating of the GUI card throughout the task, it won't show up until everything is done! Keep in mind, the idea is to keep the GUI thread's code as quickly-done as possible. Kick off the thread and exit out - keep the GUI thread free to process other events (user or otherwise.)
Putting it all together? Here's what my sample code looks like on a WinForm I created for this problem:
private void btnSmallJob_Click(object sender, EventArgs e)
{
Task myTask = new Task(SmallTaskCode);
myTask.Start(); // NOTE: I'm NOT doing a wait() on this task; don't want to hold up the GUI thread.
}
private void SmallTaskCode()
{
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Small Job" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
}
Task singleInstanceOfLargeJob;
private void btnLargeJob_Click(object sender, EventArgs e)
{
if (this.singleInstanceOfLargeJob == null || this.singleInstanceOfLargeJob.Status != TaskStatus.Running)
{
singleInstanceOfLargeJob = new Task(LargeTaskCode);
singleInstanceOfLargeJob.Start(); // NOTE: I'm NOT doing a wait() on this task; don't want to hold up the GUI thread.
return;
}
MessageBox.Show("Sorry, you can only have one instance of the large job running at once!");
// this job should only have one instance running at a time!
}
private void LargeTaskCode()
{
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job A" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job B" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
txtResults.BeginInvoke(new Action(() => txtResults.Text += DateTime.Now.ToString() + " Big Job C" + Environment.NewLine));
System.Threading.Thread.Sleep(1000);
}
Basically, I've got it two different ways. The first half is just a simple, kick-off-the-thread-and-done. The second keeps track of the Task, and doesn't allow two of the job to be running at the same time.
Anyway, hope that helps!
I am a beginner in high level programming languages. I am trying to make an WForms app for a serial port , im using VS 2010 C#
I get the following error:
Cross-thread operation not valid: Control 'rtxtDataArea' accessed from a thread other than the thread it was created on.
This happens here:
private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}
I have tried to change the color of a textbox when I receive some data.
It fires that cross thread error.
The question is why it does not fire the same error here, when I try to change the color of a label?
private void btnConnect_Click(object sender, EventArgs e)
{
if (ComPort.IsOpen)
{
disconnect();
}
else
{
connect();
rdText.ForeColor = Color.Blue;//ok, it works
}
}
; this works ; the first does not.
Why? Is not the ComPort_DataReceived_1 the same nature as btnConnect_Click ?
Or what is the reason?
I have read a lot about threads, but I understood nothing I can use, Can someone give an intuitive explanation ?
In winforms there is only one thread that may change anything on the UI like enable buttons, change text boxes, etc. Usually this is the UI thread. Quite often this is the only thread you have.
However, if you start a new thread, this thread might want to change the UI. This happens especially if this new thread fires an event that is received by your form.
Whenever you see the message accessed from a thread other than the thread it was created on, you can be almost certain this is the case.
The most easy solution to solve this is using the functions Control.IsInvokeRequired and Control.Invoke. The pattern to do this is as follows. The following function updates myButton on myForm
private void UpdateMyButton (MyType myParameter)
{
if (myButton.InvokeRequired)
{ // this is not the thread that created the button to update
this.Invoke(new MethodInvoker( () => this.UpdateMyButton(myParameter)));
// this will let the UI thread call this function with the same parameter.
}
else
{ // Now called by the UI thread: this thread may change myButton
myButton.Enabled = myParameter.ShouldButtonEnable;
myButton.Text = myParameter.ButtonText;
}
}
By the way, if you have to update several controls on your form you ought to check InvokeRequired for each of these controls. However, since they are usually created by the same UI thread it is sufficient to check for this.InvokeRequired.
Control.Invoke returns after the invoke is completed, so after all items are updated. Upon return of Invoke you can use the result of UpdateMyButton.
If you don't want your non-ui thread to wait for completion of UpdateMyButton, consider the use of Control.BeginInvoke: "hey UI thread, whenever you've got time, can you UpdateMyButton for me. Of course in that case you can't use the results of UpdateMyButton
Because "DataReceived" runs on another thread and not UI thread. You must use Invoke for that :
private void ComPort_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
recievedData = ComPort.ReadExisting(); //read all available data in the receiving buffer.
if (InvokeRequired)
{
// If not on UI thread, then invoke
Invoke(new MethodInvoker(() =>
{
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}));
}
else
{
// On UI thread, invoke not needed
// Show in the terminal window
rtxtDataArea.ForeColor = Color.Green; // error ,
rtxtDataArea.AppendText(recievedData + "\n");
}
}
I am trying to change a label on the form during a background worker process However it says unhandled exception. I've looked at the error and it said to invoke it like the accepted answer says here: Update label text in background worker winforms
I have done this successfully with changing values inside a checkbox list, but I've used the same method and for the label it won't invoke, I get the red error line in the code when I type it.
My Background Worker:
private void bw2_DoWork(object sender, DoWorkEventArgs args)
{
BackgroundWorker worker = sender as BackgroundWorker;
func.sshConnect();
for (int num = 0; num < checklist.Items.Count; num++)
{
if (checklist.GetItemChecked(num))
{
string project = checklist.Items[num].ToString();
lblStatus.Text = "Opening " + project + "..."; //error here
if (func.svnCheckoutProject(project))
{
lblStatus.Text = project + " Opened"; //same error here
func.sshRunCommand("echo " + project + " >> " + Properties.Settings.Default.serverUserFilesPath + Properties.Settings.Default.Username);
}
else
{
//error message
}
}
worker.ReportProgress(num * (100 / checklist.Items.Count));
}
}
I've tried replacing the lines with errors with this, but in visual studio it gives it a red line under invoke and won't let me build it.
lblStatus.Invoke((MethodInvoker)delegate { lblStatus.Text = "Opening " + project + "..."; });
Visual studio points me to here when the error appears: MSDN
I used this method for the checkbox list and it worked but try it for the label and it doesn't work.
Any ideas of why it isn't working or another way of doing this?
Don't update your UI controls from the DoWork event - you're on a separate thread from the UI thread. You can call Invoke, but it's really not the right place for it.
The BackgroundWorker already provides a construct for periodically updating the UI thread while the worker is running, and you're already using it - it's the ReportProgress method. That's the place you should be updating your Label from. Anything in there runs back on the main UI thread.
You can pass any object you want to the ReportProgess method:
worker.ReportProgress(num * (100 / checklist.Items.Count),
string.Format("Opening {0} ...", project));
Then cast the value back and use it in the ProgressChanged event:
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
lblStatus.Text = message;
}
In most cases, you would want to use BeginIvoke rather than Invoke
public void changeLabelText(System.Windows.Forms.Label lib, String whateva)
{
if (lib.InvokeRequired)
{
lib.BeginInvoke(new MethodInvoker(() => changeLabelText(lib, whateva)));
}
else
{
lib.Text = whateva;
}
}
and the call the method in your DoWork
private void bw_DoWork(object sender, DoWorkEventArgs args)
{
//do something
//change the text of a label lb1
changeLabelText(lb1, "I'm reporting some progress....");
}
I am trying to update 2 labels and 2 progress bars on the UI.
However it does not work, the form just freezes and nothing happens until the saving is over.
In windows forms I am used to using Invoke and making it update. How can I achieve updating in WPF?
Thanks!
public void EncryptFiles(string saveFileLocation, string saveFileTitle, string saveFileExtension)
{
using (ZipFile zip = new ZipFile())
{
zip.Password = passwordField1.Password;
zip.Encryption = EncryptionAlgorithm.WinZipAes256;
foreach (File file in dataStorage.listOfFiles)
{
zip.AddFile(file.fileLocation, String.Empty);
}
zip.SaveProgress += SaveProgress;
zip.Save(System.IO.Path.Combine(saveFileLocation, saveFileTitle + saveFileExtension));
}
}
public void SaveProgress(object sender, SaveProgressEventArgs e)
{
if (e.EventType == ZipProgressEventType.Saving_Started)
{
System.Windows.Forms.MessageBox.Show("Begin Saving: " + e.ArchiveName);
}
else if (e.EventType == ZipProgressEventType.Saving_BeforeWriteEntry)
{
labelCompressionStatus.Content = "Writing: " + e.CurrentEntry.FileName + " (" + (e.EntriesSaved + 1) + "/" + e.EntriesTotal + ")";
labelFilename.Content= "Filename:" + e.CurrentEntry.FileName;
progressBar2.Maximum = e.EntriesTotal;
progressBar2.Value = e.EntriesSaved + 1;
}
else if (e.EventType == ZipProgressEventType.Saving_EntryBytesRead)
{
progressBar1.Value = (int)((e.BytesTransferred * 100) / e.TotalBytesToTransfer);
}
else if (e.EventType == ZipProgressEventType.Saving_Completed)
{
System.Windows.Forms.MessageBox.Show("Done: " + e.ArchiveName);
}
}
Dispatcher.Invoke(new Action(() => {
// code here for updating GUI
}), DispatcherPriority.ContextIdle);
Found this and it works for me, thanks everyonne
Edit: My answer only applies if you're running your unzip operation on a thread that is different than the UI thread (does not have to be a background worker). Otherwise, furkle's answer is what you want.
Check out the WPF dispatcher. It's designed for this usage case, and is roughly analogous to Control.Invoke() in Winforms.
Unless you execute your code in a separate thread, the UI will not update until you complete your method. Changing the value of progressBar1 won't end up mattering, because the changes won't be visible until after all the work completes. The easiest way to do this would probably be to assign the work of EncryptFiles to a BackgroundWorker:
BackgroundWorker bw = new BackgroundWorker() { WorkerReportsProgress = true };
bw.DoWork += EncryptFiles;
bw.ProgressChanged += ReportEncryptSaveProgress;
Then, keep reference to bw and call bw.ReportProgress() each time the task is updated, passing the percentage of completion to the method. Given that ReportProgress occurs on the UI thread, you should put your updates to your ProgressBars therein, and the user should see the progress of saving.
Note that I'm not 100% sure how you'd want to move your work into the BackgroundWorker - I'm not positive I know how the ZipFile object's events work, so you might have to work with it a bit. It might end up easier to make the BackgroundWorker a field of your class rather than trying to pass it to SaveProgress, given that I'm assuming you can't alter SaveProgress's method signature.
You could accomplish the UI updates by using the Dispatcher, but your UI will still end up totally locked for the entirety of the operation, and that's generally good to avoid.
I've got some backgroundworker threads that are working on calculations or saving data. Every time a thread is done working I'm first adding a message to a static messagelist and then I'm showing this in a richtextbox.
The message contains the hour, a label, the message and a message type. This message type I'm using to show the message in a specific color.
Now I've got the problem that some times a 2 threads are done at the same time and try to set a message. So I got the cross-thread exception.
I know that I can solve this by using a delegate. But I'm kinda stuck here.
this is how I set my message currently:
private void SetMessages()
{
rtxtMessage.Text = "";
foreach (var message in GlobalVariables.MessageList)
{
var text = message.Date.ToShortTimeString() + " " + message.Label + ": " +
message.TheMessage;
switch (message.Type)
{
case GlobalVariables.MessageType.normal:
rtxtMessage.SelectionColor = Color.Black;
break;
case GlobalVariables.MessageType.calculation:
rtxtMessage.SelectionColor = Color.Green;
break;
case GlobalVariables.MessageType.error:
rtxtMessage.SelectionColor = Color.Red;
break;
case GlobalVariables.MessageType.warning:
rtxtMessage.SelectionColor = Color.Orange;
break;
default:
break;
}
rtxtMessage.SelectedText = text + Environment.NewLine;
rtxtMessage.ScrollToCaret();
}
pnlMessage.Visible = true;
}
So main question is how can I rewrite this to get it working with a delegate?
I interpret your question as if you are using the BackgroundWorker class for the threaded work. Then then question is how the messages come into the GlobalVariables.MessageList collection. If it is done in the RunWorkerCompleted event, you should not have any cross-threading problems, since it executes on the UI thread (this is the way I would recommend you to do). If it is updated directly from the worker process you will need to take care of synchronization and threading issues yourself (using one of the available locking mechanisms).
Update (after the comment response in the original question):
The RunWorkerCompleted should typically run on the UI thread (or perhaps rather on the thread on which RunWorkerAsync was invoked, I guess), so you should typically not need to bother about threading issues when updating the UI from that thread. However, to be really sure, you can use the following approach:
private void BackgroundWorker_RunWorkerCompleted(object sender,
System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(
new Action<object, RunWorkerCompletedEventArgs>(
BackgroundWorker_RunWorkerCompleted), sender, e);
}
else
{
// update the message list, and then call SetMessages()
SetMessages();
}
}
That said, I would urge you to pinpoint under exactly which circumstances the threading exception occurs, since it should really not happen using the RunWorkerCompleted event. I ran a test where I started a large (a couple of hundred) number of backgroundworkers simultaneously, and failed to provoke any sort of collision. It also did not use the Invoke-path in the above code sample once.
As a side note I think that you would not need to first store the messages in a list, just to clear and re-populate the textbox with all messages each time. I think you could instead change the SetMessages method into a method that takes a message as paramter and that simply adds the message to the textbox:
private void SetMessage(MyMessage message)
{
var text = message.Date.ToShortTimeString() + " " + message.Label + ": " + message.TheMessage;
switch (message.Type)
{
case GlobalVariables.MessageType.normal:
rtxtMessage.SelectionColor = Color.Black;
break;
case GlobalVariables.MessageType.calculation:
rtxtMessage.SelectionColor = Color.Green;
break;
case GlobalVariables.MessageType.error:
rtxtMessage.SelectionColor = Color.Red;
break;
case GlobalVariables.MessageType.warning:
rtxtMessage.SelectionColor = Color.Orange;
break;
default:
break;
}
rtxtMessage.SelectedText = text + Environment.NewLine;
rtxtMessage.ScrollToCaret();
pnlMessage.Visible = true;
}
You should be able to call this method straight from the RunWorkerCompleted event handler and just pass the message into the method.
You should be able to get this working by locking your static Message List like:
private void SetMessages()
{
rtxtMessage.Text = "";
lock(GlobalVariables.MessageList)
{
foreach (var message in GlobalVariables.MessageList)
{
//Rest of your code
}
pnlMessage.Visible = true;
}
}