Reputation: 81
General
I'm trying to write a very simple TCPIP client server in C# to connect to an IP Address, with a port number, and ask quite simple line commands, and then place the replies in a Gridbox, graph or other display option.
I have looked online and found a down loadable utility that does just this, written by Jayan Nair, and this appears to send the message correctly, and receive the reply ok.
The problem comes when I try and load the reply data into a richtext or GridView.
The error message I'm getting is :- System.InvalidOperationException
I have asked Microsoft Forums, and they have given me a very complicated, ambiguous and overly involved indication as to what I should do, and this involves something called INVOKE and BeginInvoke, and they whole things seems to be a project in it;s own right.
What I'm after is just an example that works, without being too complicated.
Here's the code :-
try
{
SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
int iRx = theSockId.thisSocket.EndReceive(asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
//
System.String szData = new System.String(chars);
richTextRxMessage.Text = szData; // fails
//textBox1.Text = szData; // also fails
WaitForData();
}
and here's the error message :-
base {System.Windows.Forms.TextBoxBase} = {Text = '((System.Windows.Forms.RichTextBox) (((System.Windows.Forms.RichTextBox)(richTextRxMessage)))).Text' threw an exception of type 'System.InvalidOperationException'}
Additional information is :- szData contains about 6300 characters, including tabs (9) and returns (13), and is consistant with the message sent from the server I've also tried it with using a textbox instead of richtext, same result
For those interested
Here's the Microsoft link
public void OnDataReceived(IAsyncResult asyn)
{
int InputRx;
int charLen;
char[] Inputchars;
System.Text.Decoder InputDecode;
System.String szData;
bool IfInvokeRequired;
try
{
SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
InputRx = theSockId.thisSocket.EndReceive(asyn); // get size of input array
Inputchars = new char[InputRx + 1]; // put i char array
InputDecode = System.Text.Encoding.UTF8.GetDecoder();
charLen = InputDecode.GetChars(theSockId.dataBuffer, 0, InputRx, Inputchars, 0);
szData = new System.String(Inputchars);
IfInvokeRequired = richTextRxMessage.InvokeRequired;
if (IfInvokeRequired == true)
{
richTextRxMessage.Invoke((MethodInvoker)delegate { this.Text = szData; });// fails
richTextRxMessage.BeginInvoke(new MethodInvoker(() => richTextRxMessage.Text = szData));//fails as well
}
Upvotes: 8
Views: 25954
Reputation: 2073
Here's a function I use in some of my stuff;
public static void InvokeEx<T>(this T @this, Action<T> action) where T : ISynchronizeInvoke
{
if (@this.InvokeRequired)
{
@this.Invoke(action, new object[] { @this });
}
else
{
action(@this);
}
}
It's pretty much a nice little method for dealing with any sort of invoke, and cuts down the lines of code when you're using it. To use it, simply use this;
Control.InvokeEx(f => control.DoStuff());
So for example, you might use;
richTextRxMessage.InvokeEx(f => richTextRxMessage.Text = szData);
Edit: As others have said, this error is more than likely due to accessing a control on a thread other than the one it was created on, which is why the invokes are required.
Upvotes: 1
Reputation: 19437
The issue is likely because of multi-threading as suggested on the MS forums. As you are requesting this operation in an asynchronous manner, it will create another thread that will handle the input so that you main application thread does not block.
To overcome this issue you will have to Invoke
the control whose property you wish to change. You need this, as your main application thread owns the UI, and any operation on the UI cannot be done from another thread.
The invoke is not complicated, and can be done in a few lines.
Change the following line
richTextRxMessage.Text = szData;
to
richTextRxMessage.Invoke((MethodInvoker) delegate {this.Text = szData;});
This code will ask the main ui thread to update the text for richTextRxMessage
rather than doing it itself.
Upvotes: 0
Reputation: 7804
The InvalidOperationException
that you receive will likely have the inner exception "The calling thread cannot access this object because a different thread owns it".
For the sake of debugging you can quickly set CheckForIllegalCrossThreadCalls
to false. This will stop the run-time from throwing you an exception, however just because you're not receiving the exceptions does not mean that they are not happening so this is a bad practice for distributed software but very handy for debugging.
In order to resolve your issue you need to invoke the code that interacts with the RichTextBox
on the UI Thread aka the thread the control was created on and the only thread that is allowed to run code that interacts with the control.
To invoke the code on the UI Thread you can use the following, succinct statement:
richTextRxMessage.BeginInvoke(new MethodInvoker(() => richTextRxMessage.Text = szData));
Very simply, all you are doing here is passing the statement you wish to invoke on the UI Thread by means of a delegate.
Upvotes: 5
Reputation: 2683
The Microsoft answer is not entirely incorrect, but there are other ways to do it that are somewhat simpler, although they dont look it.
Here is a snippit from my own code that might come in handy for you.
if (dirtyImage.InvokeRequired)
{
dirtyImage.Invoke(new MethodInvoker(delegate { Visible = RigX.IsDirty; }));
dirtyImage.Invoke(new MethodInvoker(delegate { Refresh(); }));
}
else
{
dirtyImage.Visible = RigX.IsDirty;
dirtyImage.Refresh();
}
note how InvokeRequired
and the dirtyImage.Invoke(...)
are used. I make it a lot simpler by creating the delegates and using a method invoker, that way I can make all the calls one liners. the delegate essentially creates a mini-method, which is then invoked using the MethodInvoker. Because the changes are on the UI thread though, they cannot be called from a background thread without using .Invoke
, its not permitted.
The way your calls could be altered in this way is:
richTextRxMessage.Invoke(new MethodInvoker(delegate { Text = szData; }));
that call also works directly from the UI thread itself, so you dont actually NEED the if statement, but it terms of 'cost', its slower than calling the properties directly.
Of Course, this only really makes a difference if you were on a background thread to begin with. If not, then you need to investigate the error itself. Perhaps there is something wrong with the text of the return, write it to a file and look at it, see if you can spot the problem. Try setting the text of the RTB manually, see if it even allows that to happen. etc etc and so on.
Upvotes: 0