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;
}
}
Related
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 have an application where I send an instruction to a micro via the PC serial port by a button click. The micro then streams back the data which fires the data received event handler. This is captured into a string.
At this point I wish to use the string's data and populate my listview box. I can do this using invoke, delegate because I am still in the data received thread.
Is there any way I can call an event handler or simple routine to do this after the thread has exited, so I don't need to use invoke, delegate? Building the routine works ok if it's triggered by a button, but I would like it to be called programmatically to complete the task.
Hope it's clear enough, it's my first post.
Edit: Here is some sample code --
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//use 28591 or("ISO-8859-1") to cover all hex bytes from 0 - 255
serialPort1.Encoding = Encoding.GetEncoding(28591);
//wait for download to complete by monitoring cts line
if (HoldData == true)
{
while (serialPort1.CtsHolding == true) ;
HoldData = false;
}
else
Thread.Sleep(50);
string text = serialPort1.ReadExisting();
switch (text[0])
{
case '?': MemLabelUpdate(); break;
case '>': WriteConfig(text); break;
case '=': SealTest(text); break;
case '<': CurrentNumber(text); break;
default: DataDownload(text); break;
}
}
The first byte of string text is an identifier as to what has come in. This in turn calls a function which populates lables on the main form using the invoke delegate method as its running within the data received thread. The default call to the download data function passes text and sorts it out as this is a mass of events. The results are then passed to my listview box into relevant columns. I want to get away from using the invoke delegate method. I need to exit the port_datareceived thread to do this and upon exit, enter my function to just update the list as below. How can i trigger this kind of event programatically.
private void btnDisplayData_Click(object sender, EventArgs e)
{
int SectionStart = 10;
int SectionEnd = 8;
listView1.Items.Clear();
listView1.View = View.Details;
listView1.GridLines = true;
//Add columns to listview
listView1.Columns.Add("Event", 80, HorizontalAlignment.Center);
listView1.Columns.Add("Time", 80, HorizontalAlignment.Center);
listView1.Columns.Add("Date", 80, HorizontalAlignment.Center);
//Print results to listview box
ListViewItem ListItem;
for (int i = 0; i < 10; i++)
{
ListItem = listView1.Items.Add(DownloadedData.Substring(SectionStart, SectionEnd));
SectionStart += 8;
ListItem.SubItems.Add(DownloadedData.Substring(SectionStart, SectionEnd));
SectionStart += 8;
ListItem.SubItems.Add(DownloadedData.Substring(SectionStart, SectionEnd));
SectionStart += 8;
}
foreach (ColumnHeader column in listView1.Columns)
{
column.Width = -2;
}
}
It looks like the event you're really interested in is PinChange. So handle that, check CTS, and then use BeginInvoke to send one message back to your UI thread, which can empty the serial port buffer (without waiting), parse the data, and update the controls.
As a further note, the DataReceived event is actually useless on the Microsoft-provided System.IO.Ports.SerialPort class. If you're interested in doing something with the data as soon as it arrives, use BeginRead with a callback.
You still haven't given any detail on the threading issue. Is the SerialPort created on the UI thread, a worker thread, or a BackgroundWorker? The SynchronizationContext is important and if the SerialPort is created on a different thread, then you have to use some kind of mechanism to Invoke or raise the data back to the UI thread to display.
The simplest solution might be just to Invoke the DownloadData method (assuming that method updates the UI or calls another method to update the UI):
switch (text[0])
{
case '?': MemLabelUpdate(); break;
case '>': WriteConfig(text); break;
case '=': SealTest(text); break;
case '<': CurrentNumber(text); break;
default:
if (InvokeRequired)
Invoke((MethodInvoker)delegate { DataDownload(text); });
else
DataDownload(text);
break;
}
Another technique would be to define your own events for the data received cases. YouOne can define an event for one or all of the cases. You may only need to define an event for the code that will access the UI, but it might be a good idea to add events for all the cases to allow you to componentize the protocol better and move the serial code into a separate class:
// Create an EventArgs based class for the data
public class DataDownloadEventArgs : EventArgs
{
public string Data { get; set; }
public DataDownloadEventArgs(string data)
{
Data = data;
}
}
public partial class Form1 : Form
{
// Event handler when data is downloaded
public event EventHandler<DataDownloadEventArgs> DataDownloaded;
// Virtual method to raise event to observers
protected virtual void OnDataDownloaded(DataDownloadEventArgs e)
{
var handler = DataDownloaded;
if (handler != null)
{
handler(this, e);
}
}
// Form constructor
public Form1()
{
InitializeComponent();
// Add handler for the download event
DataDownloaded += new EventHandler<DataDownloadEventArgs>(DisplayData);
}
// Serial port receive event
private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
// snip...
switch (text[0])
{
case '?': MemLabelUpdate(); break;
case '>': WriteConfig(text); break;
case '=': SealTest(text); break;
case '<': CurrentNumber(text); break;
default:
DataDownload(text);
OnDataDownloaded(new DataDownloadEventArgs(text));
break;
}
// snip...
}
// Change btnDisplayData_Click to the following:
private void DisplayData(object sender, DataDownloadEventArgs e)
{
// insert remaining code from btnDisplayData_Click
}
}
If you are still having issues with the UI thread and the events, you might use an extension to raise the event to the UI thread like this. Here is an article that might help as well, Cross-Thread Events.
When I use this task I can't do any thing in my application after saving the contact except showing a messagebox with a message how can I return again to my application when this task completed !!
This should work out the box. When task is finished your application takes control again. You can control task result using Completed event handler
this.saveContactTask.Completed += new EventHandler<SaveContactResult>(saveContactTask_Completed);
private void saveContactTask_Completed(object sender, SaveContactResult e)
{
switch (e.TaskResult)
{
case TaskResult.OK:
MessageBox.Show("Contact is successfully saved.");
break;
case TaskResult.Cancel:
MessageBox.Show("The user canceled the task.");
break;
case TaskResult.None:
MessageBox.Show("NO information regarding the task result is available.");
break;
}
}
Here you can find fully functional example which works exactly as you want. You may take a look and compare with your implementation
http://windowsphonegeek.com/tips/8-How-to-use-SaveContactTask-in-Windows-Phone-Mango
I have a Windows Form, which is a modal mdi child, that is suppose to be shown when some intense background work is going on, so the user cannot use any of the controls until that work is finished.
It is very simple, here is the code.
public partial class ProgressForm : Form
{
private int periodCount = 5;
public ProgressForm(String message)
{
InitializeComponent();
messageLabel.Text = message;
}
public void startThread()
{
Thread t = new Thread(new ThreadStart(doWork));
t.IsBackground = true;
t.Start();
}
void doWork()
{
while (true)
{
if (periodCount == 5)
{
periodCount = 1;
}
else
{
periodCount++;
}
switch (periodCount)
{
case 1: periodsLabel.Text = "."; break;
case 2: periodsLabel.Text = ". ."; break;
case 3: periodsLabel.Text = ". . ."; break;
case 4: periodsLabel.Text = ". . . ."; break;
case 5: periodsLabel.Text = ". . . . ."; break;
}
}
}
}
but, the periodsLabel.Text does not change as it is suppose to! How do I get it to update the UI while doing something else in the background?
ProgressForm progressForm = new ProgressForm("Your database data is being exported, please wait.");
progressForm.ShowDialog();
progressForm.startThread();
First, in my humble opinion, you should not just throw away a thread like you do.
The best practice is using a BackgroundWorker Thread.
Second, you form isn't modal at all, as you only show it using the Show() method. In order to make it a modal form, you need to make it a dialog using the ShowDialog() method.
As to why exactly your Form is crashing is quite out of scope from now on. Please consider following the following steps:
Instantiate a BackgroundWorker class;
Make the BackgroundWorker.DoWork() method do the dirty work for you;
Make sure you set the BackgroundWorker.WorkerReportsProgress = true in the component model property window in design;
Make sure you ReportProgress() using the ReportProgress(int) method.
Please see this question (C#: Populating a UI using separate threads.) and my code sample which simply explains, I think, the use of a BackgroundWorker class instance.
Note: Still looking for another example.
EDIT #1
Here's a good article on threads: Threading in C#.
'Cause Jon Skeet said so! Multi-threading in .NET: Introduction and suggestions.
It's likely that your app is crashing because you are trying to access your form elements' properties directly from a thread (e.g. when you call PeriodsLabel.Text) rather than using BeginInvoke from your thread to call a method on your form which will do the property accessing.
But it's better/simpler to use the BackgroundWorker class for these kinds of things.
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();