2

I'm currently working on a small program for home use (mainly because I'm too lazy to run down the stairs every time I want to tell my dad something).

Now as for my question:

How can I get this code to call my ReceiveText method as soon as the udpclient receives a message from a peer? (Note: There's only going to be 2 pc's running this, having both ip's hardcoded into it)


The above question has meanwhile been answered thanks to Ahmed Ilyas, however I've run into a snag which is featured on the link he posted:

The message is being sent just fine like this:

sendMe.Connect("192.168.0.121", 60500);
sendBytes = Encoding.ASCII.GetBytes(txtSend.Text);
sendMe.Send(sendBytes, sendBytes.Length);
sendMe.Close();

The problem seems to lie at the receiving end, from what I read from the exception it seems to be occurring when I try to actually use the data that was sent so I can have it properly formatted etc. for my Rich Text Box.

Here's the code which receives & parses the incoming data:

UdpClient sendMe = new UdpClient(60500);
UdpClient illReceive = new UdpClient(60501);
Byte[] sendBytes;
string returnData;
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);

...
try
{
        illReceive.BeginReceive(new AsyncCallback(RecieveText), null);
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString());
}
...

void ReceiveText(IAsyncResult res)
{
    try
    {
        System.Reflection.Assembly a = _
System.Reflection.Assembly.GetExecutingAssembly();
        System.IO.Stream s = a.GetManifestResourceStream("IPSend.ns.wav");
        SoundPlayer player = new SoundPlayer(s);
        player.Play();

        byte[] received = illReceive.EndReceive(res, ref RemoteIpEndPoint);
        returnData = Encoding.UTF8.GetString(received);
        TextRange tr = new _
TextRange(rtbPast.Document.ContentEnd, rtbPast.Document.ContentEnd);
        tr.Text = String.Format("{0} - Yorrick: {1} {2}", _
DateTime.Now.ToShortTimeString(), returnData, Environment.NewLine);
        tr.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
        tr.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Black);
        illReceive.BeginReceive(new AsyncCallback(RecieveText), null);
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString());
    }
}

PS: I'm using the MessageBox.Show for now, simply so I can clearly take a look at the exception thrown, from what I understand from those, it seems to be a problem somewhere in the ReceiveText method.

The exception being thrown: (Translation for the 1st line: "The call thread can not open this object because it is owned by another thread.") Exception


New exception after trying C.Evenhuis' code (After changing it for WPF): New Exception And the code behind this exception:

void ReceiveText(IAsyncResult res)
{
    try
    {
        byte[] received = illReceive.EndReceive(res, ref RemoteIpEndPoint);
        returnData = Encoding.UTF8.GetString(received);

        InformUser(returnData);

        illReceive.BeginReceive(new AsyncCallback(ReceiveText), null);
    }
    catch (Exception e)
    {
        MessageBox.Show(e.ToString());
    }
}

void InformUser(string data)
{
    // InvokeRequired tells you you're not on the correct thread to update the _
//rtbPast
    if (rtbPast.Dispatcher.CheckAccess())
    {
        // Call InformUser(data) again, but on the correct thread
        rtbPast.Dispatcher.Invoke(new Action<string>(InformUser), data);

        // We're done for this thread
        return;
    }

    System.Reflection.Assembly a = _
System.Reflection.Assembly.GetExecutingAssembly();
    System.IO.Stream s = a.GetManifestResourceStream("IPSend.ns.wav");
    SoundPlayer player = new SoundPlayer(s);
    player.Play();

    TextRange tr = new TextRange(rtbPast.Document.ContentEnd, _
rtbPast.Document.ContentEnd);
    tr.Text = String.Format("{0} - Pa: {1} {2}", _
DateTime.Now.ToShortTimeString(), data, Environment.NewLine);
    tr.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
    tr.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Black);
}

PS: If you need to take a look at my entire code, I've posted it on Pastebin, mainly because it's a bit too long to actually post it in here: Entire code on Pastebin

Yorrick
  • 377
  • 2
  • 8
  • 28
  • 1
    I've not experienced much with socket programming (just done a simple one in Java), but after a quick search, I've found this http://stackoverflow.com/questions/221783/udpclient-receive-right-after-send-does-not-work That means you need 2 UdpClients, one for sending and one for receiving. – King King Oct 28 '14 at 08:37
  • I see, I'll add that in then, thanks :) Now I just need a way to detect when the remote pc sends a message back so it can trigger the `RecieveText()` method – Yorrick Oct 28 '14 at 08:40
  • 1
    take a look here: http://stackoverflow.com/questions/7266101/receive-messages-continuously-using-udpclient – Ahmed ilyas Oct 28 '14 at 08:46
  • Edited original question with new code, thanks to @Ahmedilyas :) – Yorrick Oct 28 '14 at 17:40
  • 1
    You mention twice that an exception is thrown. But not one time do you say what the exception is or what the stack trace is for the exception. As far as the code you posted goes, I didn't notice any obvious fatal problems. But you should use the same encoding to send text as to receive it. And if you expect for the method named `ReceiveText` to be called when the I/O completes, you should use that name in the call to the `BeginReceive()` method, and not `RecieveText`. (I.e. if you actually posted the code you're using, then you've got a different, misspelled receive method lying around). – Peter Duniho Oct 28 '14 at 18:43
  • Fair point, I'll get a screenshot of the msgbox containing the exception in a few minutes. Also, I used the `ReceiveText` method for something else earlier, I just didn't think about changing it's name yet ;) – Yorrick Oct 28 '14 at 18:45
  • I can imagine the exception is a cross-thread operation one, because you're trying to update a control from the `ReceiveText` AsyncCallback method: http://msdn.microsoft.com/en-us/library/ms171728.aspx – C.Evenhuis Oct 28 '14 at 18:48
  • So it is that... I was thinking about that earlier but I didn't think that'd give me problems, my mistake there then, it's my first time using threads to this extent so I had no idea. Thanks for clearing it up @C.Evenhuis :) However, could you give me a hand in re-writing my code to make a Thread-safe call? Mainly because I'm a pretty bad insomniac & right now the Fibonacci sequence even seems too complicated for me >.> I could probably do it if I just continued tomorrow but I'm also kind of OCD when it comes to finishing things :( – Yorrick Oct 28 '14 at 19:09

2 Answers2

2

This issue is caused because an AsyncCallback method like ReceiveText typically runs in a different thread than the "main" thread. Controls can only be accessed from the thread they were created on.

To solve this, you'll want to split the "receive" part:

    void ReceiveText(IAsyncResult res)
    {
        try
        {
            byte[] received = illReceive.EndReceive(res, ref RemoteIpEndPoint);
            returnData = Encoding.UTF8.GetString(received);

            InformUser(returnData);

            illReceive.BeginReceive(new AsyncCallback(RecieveText), null);
        }
        catch (Exception e)
        {
            MessageBox.Show(e.ToString());
        }
    }

from the "display" part:

    void InformUser(string data)
    {
        // InvokeRequired tells you you're not on the correct thread to update the rtbPast
        if (rtbPast.InvokeRequired)
        {
            // Call InformUser(data) again, but on the correct thread
            rtbPast.Invoke(new Action<string>(InformUser), data);

            // We're done for this thread
            return;
        }

        System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
        System.IO.Stream s = a.GetManifestResourceStream("IPSend.ns.wav");
        SoundPlayer player = new SoundPlayer(s);
        player.Play();

        TextRange tr = new TextRange(rtbPast.Document.ContentEnd, rtbPast.Document.ContentEnd);
        tr.Text = String.Format("{0} - Yorrick: {1} {2}", DateTime.Now.ToShortTimeString(), data, Environment.NewLine);
        tr.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
        tr.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Black);
    }
C.Evenhuis
  • 25,996
  • 2
  • 58
  • 72
  • You missed the fact that I'm using WPF, not winforms ;) .InvokeRequired & .Invoke aren't used by controls in WPF, instead those are managed by `.Dispatcher.CheckAccess()` & `.Dispatcher.Invoke` However, after making those 2 small adjustements to your code, I'm still getting a very similar error as I did before, I just posted the image of the new exception in my OP. – Yorrick Oct 28 '14 at 20:01
  • @Yorrick Sorry, completely missed that! I saw "rtbPast", and immediately assumed the WinForms `RichTextBox` control (even though `TextRange` doesn't exist in WinForms). – C.Evenhuis Oct 29 '14 at 07:27
  • @Yorrick I don't know enough WPF to answer this question; perhaps you should also post the updated code (the one using `Dispatcher`) so I can delete this answer and others can try to solve the issue. – C.Evenhuis Oct 29 '14 at 07:30
  • Done, the code is pretty much the same except for the different syntax for `.InvokeRequired` and `.Invoke` though. – Yorrick Oct 29 '14 at 17:03
1

Yorrick, your "check access" logic in InformUser() is inverted. CheckAccess() returns true if the current thread is associated with the dispatcher. You should Invoke() only when CheckAccess() returns false. What's currently happening is you're calling InformUser(), CheckAccess() returns false, and execution falls through to the same code that was giving you trouble before because you're still accessing the control from the wrong thread.

What you need is this (negate the check):

if (!rtbPast.Dispatcher.CheckAccess()) // Invoke only when CheckAccess returns false
{
    // Call InformUser(data) again, but on the correct thread
    rtbPast.Dispatcher.Invoke(new Action<string>(InformUser), data);

    // We're done for this thread
    return;
}
Curt Nichols
  • 2,757
  • 1
  • 17
  • 24