none
Await Event RRS feed

  • Question

  • I have a race condition in a DLL I have to build where the caller sends a command to a serial attached barcode scanner via System.IO.Ports.SerialPort object and the method returns a string. However, because I'm parsing the data from the scanner in the DataReceived event I need to be able to wait until that event has fired to return anything back to the caller.

    What I would like to be able to do is implement the new async / await methodolgy, but even after looking through existing events thread and I'm still having trouble on the implementation.

    The basic method is as follows:

    public string SendData(string _command)
    {
        try
        {
            byte[] data = HexStringToByteArray(_command);
    
            // Send the binary data out the port
            comport.Write(data, 0, data.Length);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    
        return receivedData;
    }

    I'm using the DataReceived event to pull the data back upstream.

    private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        int bytes = comport.BytesToRead;
    
        byte[] buffer = new byte[bytes];
    
        comport.Read(buffer, 0, bytes);
    
        receivedData = ByteArrayToHexString(buffer);
    }

    Any help is appreciated, as I'm yet to fully grasp how to use the new tools when dealing with events.


    • Edited by SergeIbaka Friday, November 23, 2012 3:37 AM Clarity
    Friday, November 23, 2012 3:31 AM

Answers

  • Like this?

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    namespace ConsoleApplication1
    {
        class Program
        {
            static AutoResetEvent autoEvent = new AutoResetEvent(false);
            static void Main(string[] args)
            {
                SendData("send message");
                autoEvent.WaitOne();
            }
            static string SendData(string _command)
            {
               try
               {
                  byte[] data = HexStringToByteArray(_command);
                  // Send the binary data out the port
                  comport.Write(data, 0, data.Length);
               }
               catch (Exception ex)
               {
                  throw ex;
               }
                        await comport.DataReceived += (s, e) =>
                        {
                            int bytes = comport.BytesToRead;
                            byte[] buffer = new byte[bytes];
                            comport.Read(buffer, 0, bytes);
                            receivedData = ByteArrayToHexString(buffer);
                            autoEvent.Set();
                        };
                return receivedData;
    }
        }
    }


    jdweng

    Tuesday, November 27, 2012 6:15 PM

All replies

  • You don't have a race condition!  Don't process the data until you get the ALT P.  Test receive Data if it contains ALT P.  Don't process the data until you receive ALT P.  You will get another receive event when the rest of the data is received.  change the following line

    From : receivedData = ByteArrayToHexString(buffer);
    To :
    receivedData += ByteArrayToHexString(buffer);

    Remember to set the string deceiveData to "" before the next command is sent.  I usually do this in the send function

    receiveData = "";

    comport.Write(data, 0, data.Length);



    jdweng

    • Proposed as answer by Mike FengModerator Friday, November 23, 2012 11:07 AM
    • Unproposed as answer by SergeIbaka Monday, November 26, 2012 3:14 PM
    Friday, November 23, 2012 9:22 AM
  • What character would Alt P be?

    And are you proposing just letting the thread sleep until it contains that character?

    Monday, November 26, 2012 2:58 PM
  • I don't remember how to receive Alt-P.  I think it is the ascii P with either bit 6 or 7 set high.  Yo should easily be able to get the ALT P character from the receive data.

    I don't know what you program normally does when it is idle, so sleeping may be the right solution.  If it is a Form application, it will automatically handle the idle.  You could also add a DoEvent.


    jdweng

    Monday, November 26, 2012 3:32 PM
  • It's actually a DLL that's being called from Dynamics AX. Unfortunately, it will be sitting on our Citrix server and could be receiving a potentially very high number of records with unknown latency so I'm hesitant just to sleep the thread or something similar because I have no way to know if the transfer will take < 1 second or 60 seconds or more.

    Ignoring the Alt P for the moment, because until I can discern exactly which character we're talking about I can't confirm whether it's actually sent: What do you mean by 'I don't have a race condition.'

    I would think a method returning a string that's populated in another event from some kind of connection would be the definition of a race condition.

    And getting back to the original question: Is there a reason why async / await wouldn't work for my scenario? I've seen a lot of similar implementations, but none quite like what I'm looking for.

    Monday, November 26, 2012 4:58 PM
  • You wait for events by entering the application idle state, which occurs when no code is executing  on  the UI thread.  In your case remove the return receivedData statement and change the returned type to void.  Invoke a method to return the received data to from the  DataReceived event.  How do you know when you've received a complete message?
    Monday, November 26, 2012 6:33 PM
  • You don't need an a wait because you are waiting for the the end of message ALT-P before you process the data.  You need to start the ASYNC receive before you send the command so you capture the receive data so you don't have a race condition.

    Once you get the software running you can check if putting the process to sleep causes any errors.  I understand your concerns about going to sleep.  I don't think sleeping will cause a problem because the serial port event should wake up the process quick enough so you don't have a problem.  try with and without the sleep.


    jdweng

    • Marked as answer by SergeIbaka Monday, November 26, 2012 10:31 PM
    • Unmarked as answer by SergeIbaka Monday, November 26, 2012 10:31 PM
    Monday, November 26, 2012 7:15 PM
  • "How do you know when you've received a complete message?" If you're talking about the entirety of the data on the scanner, that's up to the caller. Some of these scanners have different terminators for the data and I don't want this DLL to have any knowledge of what the scanner is.

    There is no UI in this DLL: It's just a wrapper around the SerialPort object because of some limitations within AX.

    Bearing all this in mind: All I want to do is wait on the DataReceived event and I thought the async / await pattern had a way to do this. If it doesn't, just let me know and I can code around it. But I haven't used await and I thought this would be the perfect opportunity to see how it comes together.

    Monday, November 26, 2012 7:29 PM
  • The DataReceived event is called whenever there are ReceivedBytesThreshold bytes available.  What is your ReceivedBytesThreshold value.  You show a pattern of sending a command and I assume wanting to wait to receive a message.  How do you know when to return data to the caller?
    Monday, November 26, 2012 8:07 PM
  • I have never set one; it defaults to one so I've let that persist.

    Short answer is: I would like to return data back to the caller when the BytesToRead property is back to 0 after the DataReceived event is fired.

    Monday, November 26, 2012 8:59 PM
  • Who receives the data stream?  The receiving end of the communication paths need to know when the data ends so it can print or save the data into a file.  The receiving end also need to know when the finishes before it send another command to the device.

    If you are just developing a communication path that is forwarding data then the end of the file doesn't matter.  It is the two ends of a communication path that need inteligence to determine when the end of a data stream occurs.


    jdweng

    Monday, November 26, 2012 9:11 PM
  • I don't want to seem unappreciative of the help, but we're getting pretty far away from my original question.

    Within the DataReceived event, I can guarantee all the data returned from the scanner is read. What I can't guarantee is that everything is read prior to the method "SendData" returning a string to the caller.

    Is there a way to implement some kind of asynchronous function to handle this event, specifically using the async / await pattern?

    If you have some other non UI based pattern to implement, I'm open to it.

    Monday, November 26, 2012 10:26 PM
  • I have never set one; it defaults to one so I've let that persist.

    Short answer is: I would like to return data back to the caller when the BytesToRead property is back to 0 after the DataReceived event is fired.

    Depending upon the baud rate and other applications on the computer, that could be as few as 1 byte.  Your code discards any bytes received until the next command  is written.  Is that the behavior you desire?
    Monday, November 26, 2012 10:32 PM
  • Joel: At some point I needed to refresh to see your message from about 1:15 CST.

    You're right, but I'm still hazy on the async implementation. That's what I originally started this thread for.

    Monday, November 26, 2012 10:33 PM
  • Async is non blocking function so you can run other functionality in your program while the data is being transfered.  You still need to know when the data is finished so you can move onto the next operation.  The only difference between a synchronous read/write (blocking) and asynchronous read/writre (non blocking) is the synchronous read method stops until all the data is received.  blocking with a serail port is difficult because the only way of determing the end of data is to close the interface.  How do you close a serail port?  Usually this requires using the control lines.

    jdweng

    Monday, November 26, 2012 10:51 PM
  • Yes, but there's also notification when the task is completed. So if my original method were to await completion of said task before returning a string, I could guarantee that all data had been pulled off the scanner.

    There's more to the implementation than what I've posted, but I have a few error conditions and situations where the port is closed and a struct is passed back to the caller. My spec is that my code has to loop through all the available ports, because we have Citrix mapping virtual ports from the local machine and apparently our users can't be trusted to select a port name or baud rate. 

    Tuesday, November 27, 2012 2:57 PM
  • Normally when I return a string I end the string with a CR. Then I could use in the the Readline() method. Reading until you get the ALT-P is equivalent to a ReadLine() with the bar scanner. The AWAIT is ok is in the main process. Then you would need to put in the Async Read the AWAIT Set when you get the ALT-P. You need to change this statement. From : receivedData = ByteArrayToHexString(buffer); To : receivedData += ByteArrayToHexString(buffer); Then add a test for the ALT-P. ----------------------------------------------------------------------------- And add the statement below From : // Send the binary data out the port comport.Write(data, 0, data.Length); To : // Send the binary data out the port receivedData = ""; comport.Write(data, 0, data.Length);

    jdweng

    Tuesday, November 27, 2012 3:12 PM
  • Joel - I understand exactly what you're saying and that's pretty close to my work around except I'm concatenating the string in the caller.

    However, the whole point of this was I wanted to learn about the await implementation, and given your post above I'm still unclear about it. 

    Is it possible to do something like this:

    public string SendData(string _command)
    {
        try
        {
            byte[] data = HexStringToByteArray(_command);
    
            // Send the binary data out the port
            comport.Write(data, 0, data.Length);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    
        await comport.DataReceived += (s, e) =>
                        {
                            int bytes = comport.BytesToRead;
                            byte[] buffer = new byte[bytes];
                            comport.Read(buffer, 0, bytes);
                            receivedData = ByteArrayToHexString(buffer);
                        };
    
        return receivedData;
    }

    I know this isn't a great example because it's a synchronous method, it returns string, etc etc. But as pseudo code, is the idea possible? That's what I'm really trying to solve.

    Tuesday, November 27, 2012 4:11 PM
  • Like this?

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    namespace ConsoleApplication1
    {
        class Program
        {
            static AutoResetEvent autoEvent = new AutoResetEvent(false);
            static void Main(string[] args)
            {
                SendData("send message");
                autoEvent.WaitOne();
            }
            static string SendData(string _command)
            {
               try
               {
                  byte[] data = HexStringToByteArray(_command);
                  // Send the binary data out the port
                  comport.Write(data, 0, data.Length);
               }
               catch (Exception ex)
               {
                  throw ex;
               }
                        await comport.DataReceived += (s, e) =>
                        {
                            int bytes = comport.BytesToRead;
                            byte[] buffer = new byte[bytes];
                            comport.Read(buffer, 0, bytes);
                            receivedData = ByteArrayToHexString(buffer);
                            autoEvent.Set();
                        };
                return receivedData;
    }
        }
    }


    jdweng

    Tuesday, November 27, 2012 6:15 PM
  • What do you want for Christmas?
    Tuesday, November 27, 2012 8:02 PM