SampleApp: Developing an asynchronous IBM Spectrum Symphony C# client

The purpose of an asynchronous client is to get the output as soon as it is available. The client thread does not need to be blocked once the input data is sent and can perform other actions.

About this task

In this tutorial, you will complete these tasks:
  1. Build the sample client and service
  2. Package the sample service and add the application
  3. Run the sample client and service
  4. Walk through the code

Procedure

  1. Build the sample client and service.
    1. Navigate to the %SOAM_HOME%\version\samples\DotNet\CS\SampleApp directory under IBM® Spectrum Symphony Developer Edition.
    2. Open the Visual C#.NET solution file that is supported by your version of Visual Studio.
    3. Build the solution files by pressing ctrl+shift+B.

      Compiled executables and libraries are in the %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\output directory.

  2. Package the sample service and add the application.

    If you have not already packaged and added the sample application, refer to the synchronous client tutorial for more details.

  3. Run the sample client and service.

    To run the service, run the client application. The service that a client application uses is specified in the application profile. Before running the sample client, ensure that the AsyncClient project is set as the StartUp project in Visual C#.NET.

    1. Press F5 to run the application.

      The client starts and the system starts the corresponding service. The client displays messages in the console window indicating that it is running.

  4. Walk through the code.

    Review the sample client application code to learn how you can understand the differences between a synchronous client and an asynchronous client.

    1. Locate the code samples:
      • Solution file (Visual Studio): %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\sampleApplication.NET.<version>.sln

        or

        %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\sampleApplication.NET64.<version>.sln

        where <version> is the version of Visual Studio.

      • Client: %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\AsyncClient\AsyncClient.cs
      • Input or output object: %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\Common\MyMessage.cs
      • Service: %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\Service\SampleService.cs
      • Application profile: The service required to compute the input data along with additional application parameters are defined in the application profile: %SOAM_HOME%\version\samples\DotNet\CS\SampleApp\SampleAppDotNetCS.xml
    2. Understand what the sample does.

      The client application sample sends 10 input messages with the data Hello Grid !! and retrieves the results.

      Results are returned asynchronously with a callback interface provided by the client to the API. Methods on this interface are called from threads within the API when certain events occur. In the sample, the events are:
      • When there is an error at the session level
      • When results return from the middleware
    3. Understand code differences between synchronous and asynchronous clients.
      An asynchronous client is very similar to a synchronous client. The only differences are:
      • You need to specify a callback when creating a session
      • You specify a different flag to indicate asynchronous when you create a session
      • Retrieval of replies
      1. Initialize the client and implement the MyMessage class:

        As in the synchronous client tutorial, initialize the client and implement the MyMessage class to handle the input or output data; refer to Your First Synchronous IBM Spectrum Symphony C# Client and Service, specifically:

        • Step 1: Initialize the client
        • Step 2: Implement the MyMessage class
      2. Implement the response handler method to retrieve output messages.

        With an asynchronous client, when a task is completed by the service, there must be a means of communicating this status back to the client. The response handler is implemented for this purpose. It is called by the middleware each time a service completes a task.

        In this sample, the AsyncClientOnResponse() method is the response handler. The method accepts the TaskOutputHandle as an input argument, which is passed to the method by the middleware whenever the respective task has completed.

        Extract the message from the task result using the GetTaskOutput() method. Display the task ID, internal ID (taskCount), and output message.

        Increment the m_numReceivedTasks variable. Use the lock keyword to ensure that another thread does not try to increment the variable while it is being accessed.

        The m_eventOccured.Set() method releases the waiting main execution thread of the client.
        private void AsyncClientOnResponse( TaskOutputHandle output )
        {
               // check for success of task
               if ( output.IsSuccessful == true )
               {
                   // get the message returned from the service
                   MyMessage outputMessage = output.GetTaskOutput() as MyMessage;
                   if(outputMessage == null)
                        {
                            throw new SoamException("Have got wrong type of outputMessage object."); 
                        }
        
                   // display content of reply
                   Console.WriteLine("Task Succeeded [" + output.Id + "]" );
                   Console.WriteLine("Task Internal ID : " + outputMessage.Id );
                   Console.WriteLine(outputMessage.StringMessage );
               }
               else
               {
                   // get the exception associated with this task
                   SoamException ex = output.Exception;
                   Console.WriteLine( ex.ToString() );
               }
                
               lock(this)
               {            
                   m_numReceivedTasks++;
               }
               m_eventOccured.Set();
        }
        
      3. Implement the exception handler method (callback).

        The exception handler method is called by the API when an exception of type SoamException occurs within the scope of a session.

        Print out the exception message and set the Boolean error flag (m_noErrorReported) to false. Use the lock keyword to ensure that another thread does not try to set the flag while it is being accessed.

        private void AsyncClientOnException(SoamException exception)
        {
               Console.WriteLine( "Soam exception caught ... " + exception.ToString());
               lock(this)
               {
                    m_noErrorReported = false;
               }
               m_eventOccured.Set();
        }
        
      4. Connect to an application.

        To send data to be calculated in the form of input messages, you connect to an application; refer to Step 3: Connect to an application of the synchronous client tutorial.

      5. Create a session to group tasks.

        A session is a way of logically grouping tasks that are sent to a service for execution. The tasks are sent and received asynchronously.

        When creating a session, you need to specify the session attributes by using the SessionCreationAttributes object. In this sample, we create a SessionCreationAttributes object called attributes and set four parameters in the object.

        In this example, we set the following parameters:
        • The first parameter is the session description. This is optional. The session description can be any descriptive name you want to assign to your session. It is for information purposes, such as in the command-line interface.
        • The second parameter is the session type. The session type is optional. You can leave this parameter blank and system default values are used for your session.
          Important:

          The session type must be the same session type as defined in your application profile.

          In the application profile, with the session type, you define characteristics for the session.

        • The third parameter is the session flag. When creating an asynchronous session, set the flag to SessionFlags.ReceiveAsync. This flag indicates to IBM Spectrum Symphony that this is an asynchronous session.
        • The fourth parameter is the callback object.
        ...
        try
        {
        // Set up session attributes
                                SessionCreationAttributes attributes = new
                                SessionCreationAttributes();
                                attributes.SessionName="mySession";
                                attributes.SessionType="ShortRunningTasks";
                                attributes.SessionFlags = SessionFlags.ReceiveAsync;
                                attributes.SessionCallback = callback;
        
                                // Create an asynchronous session
                                session = connection.CreateSession(attributes);
        ...
        }
        ...
        
      6. Associate event handlers with the events.

        Associate the event handler method (AsyncClientOnResponse) with the OnResponse event; refer to Step 2: Implement the response handler method to retrieve output messages. This is necessary so that the OnResponse event knows which method to execute when the event is triggered. The method is called by the API whenever a task response is ready. Similarly, associate the AsyncClientOnException() method with the OnException event to handle exceptions of type SoamException if they occur. This method is called by the API when an exception occurs within the scope of the session; refer to Step 3: Implement the exception handler method (callback).

        ...
         // Create a SessionCallback instance and register event handlers
         SessionCallback callback = new SessionCallback();
         callback.OnResponse += new SessionCallback.ResponseHandler(AsyncClientOnResponse);
         callback.OnException += new SessionCallback.ExceptionHandler(AsyncClientOnException);
        ...
        
      7. Send input data to be processed.

        In this step, we create 10 input messages to be processed by the service. We call the MyMessage constructor and pass three input parameters: ID (taskCount), the Boolean value (false) to indicate asynchronous communication, and a message string (Hello Grid !!). When a message is sent, a task input handle is returned. This task input handle contains the ID for the task that was created for this input message.

        ...
        int numTasksToSend = 10;
        for (int taskCount = 0; taskCount < numTasksToSend; taskCount++)
        {
               // Create a message
               MyMessage inputMessage = new MyMessage(taskCount, false, Hello Grid !!);
        
               TaskSubmissionAttributes taskAttr = new TaskSubmissionAttributes();
               taskAttr.SetTaskInput(inputMessage);
        
               // send it
               TaskInputHandle input = session.SendTaskInput(inputMessage);
        
               // retrieve and print task ID
               Console.WriteLine( "task submitted with ID: " + input.Id);                   
        }
        ...
        
      8. Wait for replies before closing the session.

        After all 10 tasks (messages) have been sent to the service, the main client execution thread must wait for all tasks to be processed before closing the session. As each task is completed by the service, the m_numReceivedTasks variable is incremented; refer to Step 2: Implement the response handler method to retrieve output messages. The WaitForComplete() method is used to suspend the main client execution thread until all messages are received. The method contains a loop that checks if the number of replies equals the total number of tasks sent; if they are not equal, the thread blocks by calling m_eventOccured.WaitOne() until it is signalled to resume execution. The thread is released by calling m_eventOccured.Set() each time a task is completed or if an exception occurs. When all the replies have been received, close the session.

        Important: As is the case with the connection object, the creation and usage of the session object, i.e., sending and receiving data, must be scoped in a try-finally block. The finally block, with the session.Close() method, ensures that the session is always closed whether exceptional behavior occurs or not. Failure to close the session causes the session to continue to occupy middleware resources.
        ...
        private void WaitForComplete(int taskCount)
        {
               while(true)
               {
                   bool shouldWait;
                   lock(this)
                   {
                       shouldWait = (m_numReceivedTasks < taskCount) && m_noErrorReported;
                   }
                   if (shouldWait)
                   {
                       m_eventOccured.WaitOne();
                   }
                   else
                   {
                       break;
                   }
               }
        }
        ...
        
        ...
        }finally
        {
             // mandatory session close
             if (session != null)
             {
                 session.Close();
             }
        ...
        
      9. Uninitialize.
        Always uninitialize the client API at the end of all API calls. If you do not call uninitialize, the client API remains in an undefined state and resources used by the client are held indefinitely.
        Important: Once you uninitialize, all objects become invalid. For example, you can no longer create a session or send an input message.
        ...
        SoamFactory.Uninitialize();
        ...