Cross-thread operation not valid

  • Thread starter alphatommy_at_hotmail_dot_com
  • Start date
A

alphatommy_at_hotmail_dot_com

Hi,

I get a cross-thread operation not valid error while trying to draw
shapes within an UART data receive event. From reading some of the
posts, I gather is becuase I did not draw the shapes within the thread
that created the windows form. Currently, I am bypassing this by
using public variables, getting the values in the UART data receive
event and getting those values to draw shapes in the thread that
created the windows form. If I want to draw the shapes when I receive
a command from my UART, is there a way I can do it without doing it in
the thread that created the windows form? Thanks.
 
P

Peter Duniho

alphatommy_at_hotmail_dot_com said:
I get a cross-thread operation not valid error while trying to draw
shapes within an UART data receive event. From reading some of the
posts, I gather is becuase I did not draw the shapes within the thread
that created the windows form.

Correct. All access to things that ultimate result in going through the
window messaging system has to be done on the same thread that created
the UI object that you're accessing.
Currently, I am bypassing this by
using public variables, getting the values in the UART data receive
event and getting those values to draw shapes in the thread that
created the windows form.

This is pretty close to what you need to do anyway. See below.
If I want to draw the shapes when I receive
a command from my UART, is there a way I can do it without doing it in
the thread that created the windows form? Thanks.

No. But you can use the Control.Invoke() method to get into the thread
that creating the windows form, where you can then draw what you like.

You don't specify how you are drawing the shapes, but do keep in mind
that if you are literally drawing them (i.e. get a Graphics instance,
call methods to draw to the screen), then you are going to run into new
problems later. A common beginner mistake is to think that windows are
buffered; this results in people drawing to a window and expecting the
result to be persistent.

What you really need to do is store data used for drawing, and then use
Control.Invalidate() on the form to signal that it needs to redraw.
Then in the Paint event or OnPaint method (either works fine generally)
do the actual drawing you need to do.

Assuming you are copying data from the UART thread to the form, then all
that actually remains (sort of) is to call Invalidate().

The Invalidate() method still needs to be called via Invoke() and, while
not strictly required, because it's a simple way to synchronize access
to the form class you may also want to use Invoke() in order to store
the data needed for drawing (you have to use _some_ kind of
synchronization method, so if you're going to call Invoke() anyway you
might as well take advantage of that).

I wrote "sort of" above because if you're not properly synchronizing
thread access to the variables used to store the data, you really do
need to update the data copying aspect as well so that it's synchronized
(for example, doing it in the same method where you call Invalidate(),
as I mentioned above).

Pete
 
A

alphatommy_at_hotmail_dot_com

Hi Pete,

Thanks for the feedback. Below is what I did base on your comments
and other source that I found:

1. Create a delegate: delegate void Invoker (object sender,
SerialDataReceiveEventArgs e);

2. Serial port data receive event

private void PortCOM1_DataReceived(object sender,
SerialDataReceivedEventArgs e)
{

//define variables

//get and format data from UART

if (this.InvokeRequired)
{
this.BeginInvoke(new Invoker(PortCOM1_DataReceived),
sender, e);
return;
}

Graphics dc = this.CreateGraphics();
this.Invalidate();

//draw base on data from UART

}


The error that I am getting now is "TargetInvocation Exception was
unhandled" Please advise if your comments match what i did. Also,
how could I resolve the current pop up error. Thanks.
 
A

alphatommy_at_hotmail_dot_com

Hi Pete,

Thanks for the feedback. Below is what I did base on your comments
and other source that I found:

1. Create a delegate: delegate void Invoker (object sender,
SerialDataReceiveEventArgs e);

2. Serial port data receive event

private void PortCOM1_DataReceived(object sender,
SerialDataReceivedEventArgs e)
{

//define variables

//get and format data from UART

if (this.InvokeRequired)
{
this.BeginInvoke(new Invoker(PortCOM1_DataReceived),
sender, e);
return;
}

Graphics dc = this.CreateGraphics();
this.Invalidate();

//draw base on data from UART

}

The error that I am getting now is "TargetInvocation Exception was
unhandled" Please advise if your comments match what i did. Also,
how could I resolve the current pop up error. Thanks.












- Show quoted text -

Hi,

I should also mention that my app has multiple forms. I am using a
singleton and that UART receive data event and drawings are done in
the 2nd form. Thanks.
 
P

Peter Duniho

alphatommy_at_hotmail_dot_com said:
[...]
The error that I am getting now is "TargetInvocation Exception was
unhandled" Please advise if your comments match what i did. Also,
how could I resolve the current pop up error. Thanks.

It's hard to say without having the complete code, but I suspect your
exception is happening because the call to BeginInvoke() is incorrect.
The parameters need to be in a single array of objects:

this.BeginInvoke(new Invoke(PortCom1_DataReceived), new object[] {
sender, e});

I don't understand how the code you posted even compiles. There's not
an overload for BeginInvoke that takes three parameters.

Some other comments:

* As I mentioned in my previous post, you shouldn't be drawing in
the invoked method. Instead, all that should happen from the invoked
method is signaling that an update needs to happen by calling
Invalidate(). The data required to correctly draw the form should be
stored somewhere, and the form should handle drawing only in the OnPaint
method or an event handler for the Paint event.

* In addition to the above, the form _must_ be prepared to draw
itself _at any time_. This means that it's not sufficient to just draw
it once directly from the UART thread, even if invoked. The UART thread
must somehow persistently create data that the form can always use in
order to draw itself.

* One possible approach to persisting the data is to go ahead and
let the UART thread do the drawing, but instead of doing so to the form,
draw into a Bitmap object. Once the Bitmap object has been updated,
then the UART thread would use Invoke() to invalidate the form, and the
form would then use the already-drawn Bitmap object to actually draw the
form graphics (ie, a single call to the Graphics.DrawImage() method).

* At the very least, if you decide to ignore all that and continue
to do all of the drawing directly from the UART thread, there is no need
to call Invalidate() before doing the actual drawing. At best it's a
"nop", and at worst (depending on the exact timing) it could result in
the drawing you do being erased immediately after you draw.

* Finally, I'm not a big fan of the "InvokeRequired" pattern. That
is, I create code that either is or is not intended to be called from a
different thread. If it is, then it always calls Invoke() or
BeginInvoke(). If it's not, then it always just does whatever it's
supposed to do. This is a matter of personal preference, but I like to
mention it because MSDN seems so set on this idea of having a single
method handle both the invocation and the actual work. If you really
want a single method that is dual purpose, then just always call
Invoke() or BeginInvoke(). It's not harmful to do so from within the
owning thread.

You can do it MSDN's way without hurting anything; just don't think you
_have_ to do it that way. If you, like I do, feel that having a single
method do two different things is poor design, feel free to break out
the functionality into the code that invokes and the code that does the
work.

Pete
 

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