Cross Thread Exception after reading Asynchronous Serial Port

M

Mo

I am trying to set a text box value when data is received from the com
port (barcode reader). I am getting the following error when I try to
set the text box TXNumber after data is received

Cross-thread operation not valid: Control 'TXNumber' accessed from a
thread other than the thread it was created on.

Any ideas how to work around this problem?

Thanks
Here is my code
________ Initialize Scanner _______________
public void Initialize_Scanner()
{
sp.BaudRate = 9600;
sp.Parity = Parity.None;
sp.DataBits = 8;
sp.StopBits = StopBits.One;
sp.ReadTimeout = 1500;
sp.DataReceived += new
SerialDataReceivedEventHandler(sp_DataReceived);
sp.Open();

}
________ Data Received? ______________
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{

string ret = "\r";
string tempstring = sp.ReadLine().Replace(ret, "");
sp.Close();
this.TXNumber.Text = tempstring;

if (tempstring.Length> 0)
{
Run_process();
sp.Open();
}


}
 
G

Guest

Hi Mo,
you can only modify a control from the thread on which it was created. So
in your event handler sp_DataReceived you need to call the Invoke method on
the textbox passing in a delegate to execute on the thread that created the
control i.e.

public void Initialize_Scanner()
{
sp.BaudRate = 9600;
sp.Parity = Parity.None;
sp.DataBits = 8;
sp.StopBits = StopBits.One;
sp.ReadTimeout = 1500;
sp.DataReceived += new
SerialDataReceivedEventHandler(sp_DataReceived);
sp.Open();

}

void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{

string ret = "\r";
string tempstring = sp.ReadLine().Replace(ret, "");
sp.Close();

//Will update the text box.
this.TXNumber.Invoke(new SetTextValueHander(SetTextValue)));

if (tempstring.Length> 0)
{
Run_process();
sp.Open();
}
}

delegate void SetTextValueHandler(string value);

void SetTextValue(string value)
{
this.TXNumber.Text = value;
}

The Invoke is syncronous, if you want to update the GUI asyncronously then
you can call BeginInvoke.

Mark.
 
M

Mo

Thank you for the response. This process is still a mystery to me. I am
getting Mthod name expected in the line

this.TXNumber.Invoke(new SetTextValueHander(SetTextValue(tempstring)));

Any Ideas?

The code to look like:

delegate void SetTextValueHandler(string value);
void SetTextValue(string value)
{
this.TXNumber.Text = value;
}

void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string ret = "\r";
string tempstring = sp.ReadLine().Replace(ret, "");
sp.Close();

this.TXNumber.Invoke(new
SetTextValueHander(SetTextValue(tempstring)));


if (tempstring.Length> 0)
{
Run_process();
sp.Open();
}

}
 
G

Guest

Hi Mo,
sorry, the sample code I sent previously was not complete - note to self
"don't post when very late :)" when you call Invoke if you pass parameters
to your delegate then you also need to specify those in the call to invoke,
as values of an object array, so:
this.TXNumber.Invoke(new
SetTextValueHander(SetTextValue(tempstring)));

would be:

this.TXNumber.Invoke(new SetTextValueHandler(SetTextValue(tempString)), new
object[]{tempString});

The idea behind calling invoke is that you can only modify a control on the
same thread that created the control. The sp_DataReceived function is called
in the context of a different thread, one that is used to receive the data
from your serial port, so calling invoke will swap control back to the thread
that created the textbox (Since you are calling Invoke from the textbox
object). The delegate is like a strongly typed function pointer, the
delegate defines the method signature that it can point to, in our case a
method which has a return of void and takes a string parameter, so saying:

new SetTextValueHandler(SetTextValue)

is creating a function pointer in effect to the SetTextValue that the
textbox control should call once it gets control.

In your case you are processing data so either you need to make sure the
call to the method you call is fast or you can use the BeginInvoke which is
asyncronous and does not wait for the method pointed to by the delegate to
complete.

Hope that helps.

Thanks
Mark.
 
M

Mo

Thank you Mark, Great post. had to change the synatx to

this.TXNumber.Invoke(new SetTextValueHandler(SetTextValue), new
object[] { tempstring });

And it works like a charm which brings me to the nex problem along the
same line. I am trying to execute a method which is calling a couple of
other methods

public void Run_process()
{
Generate_Label(TXNumber.Text);
Get_P1(TXNumber.Text);
Process_P1(TXNumber.Text);
}
public void Generate_Label(string TXNumber)
{
//do something
}
etc...

and I am gettiing the same error wen I call these methods "//do
Something" . How do I go about invoking these methods in my main form?

Thank you for all your help.
Mo
 
G

Guest

Hi Mo,
in general you can do something like the following pattern when calling a
method and you think it could be called outside of the context of the main UI
thread, assuming in the case below that "this" refers to the form class
instance:

delegate void TextParameterHandler(string value);

void SetMyText(string value)
{
//Check to see if invoke is required to change context
//to the main UI thread.
if(this.InvokeRequired)
{
//Call the same method in the context of the main UI thread.
this.Invoke(new TextParameterHandler(SetMyText), new object[]{value});
}
else
{
//calling thread is same as the one that created
//the controls, we can update safely.

myTextBox.Text = value;
myLabel.Text = value;
}
}


See http://msdn2.microsoft.com/en-us/library/zyzhdc6b.aspx for more info and
also John Skeet has a good article on this:
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml

Hope that helps.
Mark.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top