On-boarding a Symphony application with Visual Studio

Goal

This tutorial walks you through the steps for on-boarding a sample application using the Symphony add-in for Visual Studio. After completing the tutorial, your sample application will be able to perform calculations in parallel on the grid. Familiarity with the Visual Studio IDE is recommended.

Prerequisites

The prerequisites for on-boarding the sample application are:

  • Visual Studio 2008 Professional/2010 Professional

  • Symphony DE 5.1 or higher installed and running

Install the Symphony add-in for Visual Studio

The Symphony add-in and extensions are automatically installed in Visual Studio during installation of the Symphony DE package when the Visual Studio add-in option is selected. Visual Studio 2008 or 2010 must be installed on the development host prior to installing Symphony DE.

Sample applications

Two on-boarding application samples are provided with the Symphony DE package. One sample is a basic calculator program that calculates the interest for different principal amounts, interest rates, and durations. This sample is organized into a main program and a separate class library where the calculations are performed. In the context of Symphony, the main program and class library represent the client and service, respectively. This sample also serves as a foundation for this tutorial as you prepare it for on-boarding onto the grid.

The second sample reflects the same application as in the first sample, but it has already been updated and grid-enabled. This sample shows you what your first sample should look like once you have completed the on-boarding process. Use this sample to see how calculations are performed on the grid or as a handy reference if you encounter any difficulty on-boarding the first sample.

Note:

The code samples are intended to be run in a 32-bit environment only.

The sample applications are located at:

%SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator.

About this tutorial

This tutorial refers to the sample applications included in the Symphony DE package at:

  • %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\1-Before (sample before on-boarding process)

  • %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\2-After (sample after on-boarding process, including optimization)

This tutorial was prepared using the Visual Studio 2008 Professional version but it should equally apply to Visual Studio 2010 Professional; any differences between the two Visual Studio versions relevant to this tutorial will be noted.

On-board an existing C#.NET application

This section describes the steps for on-boarding an existing C#.NET application using the Symphony project wizard in Visual Studio.

Step 1: Test the sample

Before you start the on-boarding process, run the sample application to ensure your Visual Studio development environment is working properly.

  1. Locate the sample solution folder at %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\1-Before. If you are using Visual Studio 2008, double-click BasicCalculator_2008.sln to open the solution; if you are using Visual Studio 2010, double-click BasicCalculator_2010.sln.
  2. Select Debug > Start Without Debugging.

    Visual Studio builds the solution and executes the code.

  3. If your command window displays the following output, your development environment is working properly and you are all set to begin the on-boarding process. Proceed to Step 2.

Step 2: Add the .NET class library to the solution

The following steps show you how to take the .NET class library that you built during the previous step and add it as a project to the sample solution.

Important:

It is recommended that you back up the sample Visual Studio solution before proceeding with the following steps in case you need to revert to the original files.

  1. In the Solution Explorer, right-click the solution. Select Add > New Project.

    The Add New Project dialog displays.

  2. Make the following selections:
    1. In the Project types pane, select Symphony.
    2. In the Templates pane, select Grid Enabled Library.
  3. In the Name textbox, enter BasicGridCalculator.
    Note:

    The project name is used as a namespace in the generated code. To avoid potential conflicts, do not choose a project name that is used as a namespace or class in the original code.

  4. Click OK.

    The on-boarding wizard launches.

    The wizard adds the following three projects to the solution:

    • BasicGridCalculatorProxy (proxy class)

    • BasicGridCalculatorService (computational logic)

    • BasicGridCalculatorTransport (serializes/de-serializes proxy calls to/from the service)

  5. Click Add.

    The Open file dialog displays.

  6. Select the .NET class library BasicCalculator.dll that you built previously at BasicCalculator\bin\Debug\.
  7. Click Open.

    The .NET class library is added to the list of .NET assemblies in the wizard.

  8. If we had additional items such as dependent libraries or configuration files, we would select the Library Dependencies tab and add them here. Click Next. Proceed to Step 3.

Step 3: Expose the members in the .NET class library so that you can access them via the proxy

Exposing the methods, and properties of the .NET class library enables you to access them in the proxy so that you can interact with the grid-enabled class library as if it was a local object. Note that the wizard exposes all members of a selected class.

  1. In the wizard’s object explorer, expand BasicCalculator.dll. Select the Calculator namespace. Ensure BasicCalculator under Classes in namespace "Calculator" is checked.
  2. Click Next.
  3. Fully expand BasicCalculator.dll and select BasicCalculator class.

    A list of the class's properties/methods and their respective roles displays. These are the methods and properties that will be exposed in the proxy object so that you can interact with the service; refer to the Simplified application on-boarding with Visual Studio feature reference in the Application Development Guide for a detailed description of property/method roles.

  4. Click Next.
  5. In the Application Name textbox, enter BasicGridCalculatorApp. If we were connected to the grid, we would also need to specify a valid consumer to which the application belongs.
  6. Click Next.

    The wizard completes the code and project generation process and summarizes the generation activities. The sample application is registered and deployed to the grid.

  7. Click Next.
  8. At this point, all code and project generation is complete but if you were able to run the client program CalculateInterest.exe, it would still call the methods of the local .NET class library instead of the library on the grid. You can either manually add the proxy reference to the client and update the code to refer to the generated proxy class or you can let the wizard do it for you. Let’s have the wizard do the changes for us.

    In the Projects to be updated list, select CalculateInterest.NET.2008.

    Note:

    During this step, it is important to select only the projects where you want the wizard to update the client code and add references to the proxy that was just generated and not select any of the other libraries currently being on-boarded.

  9. Click Next.

    The wizard updates and builds the CalculateInterest.NET.2008 project.

    If you were to look at the code in the Program.cs file, you would see that a reference to the BasicGridCalculator class has been added and we are now using the BasicCalculator proxy class in our code instead..

    using BasicGridCalculator;
    ...
    //Main program
    ...
    calculator = new BasicGridCalculator.BasicCalculator();
  10. Click Next.

    We have now reached the last page of the wizard. All the activities performed by the wizard are recorded in build.log at the following locations:

    1. proxy build log

      %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\1-Before\BasicGridCalculator\BasicGridCalculatorProxy

    2. service build log

      %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\1-Before\BasicGridCalculator\BasicGridCalculatorService

    3. transport build log

      %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\1-Before\BasicGridCalculator\BasicGridCalculatorTransport

  11. Check that both Open the generated Readme.html after the wizard exits and Open the generated Report.txt after the wizard exits are selected.
  12. Click Finish.

    The wizard closes and the Readme.html and Report.txt pages are displayed. The Readme file provides descriptions and code samples of the properties and methods that are available through the proxy. The Readme uses a color-coding scheme to distinguish the classes and methods, as follows:

    • Blue: links to proxy classes or methods that are derived from the original user-defined classes or methods

    • Green: links to classes or methods on these proxy objects that have been added by the wizard to make the classes or methods more grid-capable.

    The Report.txt file summarizes all the activities performed by the wizard during the on-boarding process including client code updates, creating and building projects, deploying service packages, and registering the application.

    Proceed to Step 4

Step 4: Add configuration files to handle mixed-mode assemblies

This step applies only to the Visual Studio 2010 IDE. If you are using Visual Studio 2008, proceed to Step 5.

As of .NET runtime 4.0, a change was made to no longer load mixed assemblies automatically. In order to load mixed assemblies, the runtime must be explicitly instructed through the use of a configuration file.Since all on-boarded applications still rely on the Symphony API (which is implemented as a mixed assembly), both the client and the service package to be deployed to the grid must be associated with a configuration file to instruct the runtime to load the mixed assembly.. This means that as long as a .Net binary is being loaded by the .NET 4.0 framework, as a minimum, the startup attribute useLegacyV2RuntimeActivationPolicy must be set to true in the configuration file. For convenience, we will use the configuration files that are packaged with the sample.

  1. To associate a ".config" file with the client, copy the file CalculateInterest.exe.config located at %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\2-After\CalculateInterest to the same location as the Calculate.exe executable.
  2. To associate a ".config" file with the service that will run on the grid, add file BasicGridCalculatorService.exe.config to the service package on the grid.
    1. Right click on the BasicGridCalculatorService project.

      The project context menu displays.

    2. Select Platform Symphony > Application Details.

      The Application Details dialog displays.

    3. Under the section Files included in package, click the Add button.
    4. Browse and select the configuration file BasicGridCalculatorService.exe.config located at %SOAM_HOME%\5.1\samples\AppOnboarding\dotnet\CS\BasicCalculator\2-After\BasicGridCalculator\BasicGridCalculatorService.
    5. Click Create and Deploy Service Package.

      The service package is re-deployed to the grid.

  3. Proceed to Step 5.

Step 5: Run the sample on the grid

Before we go any further, let’s test the application on the grid to ensure it is working properly.

  1. Select Debug > Start Without Debugging.

    Visual Studio builds the solution and executes the code.

  2. Verify that your command window displays the same output as in Step 1.
  3. You can also verify that the workload was successfully completed by accessing the Platform Management Console (PMC) through the Symphony menu extensions for Visual Studio. Before you can connect to the PMC, you must first select the respective service for the application. This is necessary since each service project stores its respective application profile and service package and an on-boarded application can generate multiple service projects.

    In the Solution Explorer, select the BasicGridCalculatorService project.

  4. Select Symphony > Platform Management Console > Monitor Workload.
  5. In the PMC, check that the Sessions page shows 1 session with 16 tasks done. For an explanation on how on-boarded application methods are mapped to workload, refer to Simplified application on-boarding with Visual Studio in the Application Development Guide.

    Proceed to Step 6.

Step 6: Free up Symphony resources

It is good programming practice to free up Symphony resources when they are no longer needed. For example, the client may have used a proxy and has no intention of using it anymore but the client is expected to run for several minutes after that. During this time, the session would remain open unnecessarily in Symphony. To address this issue, we can call the Dispose() method on the proxy object at a point where we no longer need the proxy and just before the reference goes out of scope.

...
     Calculate(principalAmounts, calculator);
}
catch (Exception E)
{
     Console.WriteLine("{0}\n{1}", E.Message, E.StackTrace);
}
finally 
{
    if (null != calculator)
                {
                    calculator.Dispose();
                }
}

Refer to Simplified application on-boarding with Visual Studio in the Application Development Guide for more information about the Dispose() method.

Next, we will look at how to optimize the client code to maximize the benefits of running our application on the grid.

Step 7: Optimizing the code for the grid

In the previous step where we ran our application on the grid, our client interacted with the grid-enabled library and the calculations were actually performed on the grid. But the interaction between the client and the service was synchronous in nature as each input task was sent to the service, one at a time, in a blocking mode.

The following code snippet was taken from the client file Program.cs.

foreach (double amount in principalAmounts)
{
    double interest = calculator.SimpleInterest(amount);
    Console.WriteLine("Principal : ${0},\t Interest: ${1}",
    amount, interest);
}

In the code, we can see that the main execution thread loops through the principal amounts, one at a time, and waits for the result of each calculation to return before it calls the next method. Although the code executes and results are collected, this approach does not benefit from running on the grid. Benefits can only be realized when you execute methods in parallel across multiple computing resources available on the grid.

Asynchronous Programming Model

One of the simple ways to run our calculations in parallel is by using the .NET Asynchronous Programming Model (APM) convention and break our loop into two steps that use the appropriate methods to begin and end our calculation methods. In the code snippet from the previous section, instead of using the SimpleInterest() method on our proxy, we will use the BeginSimpleInterest() method that will not block our execution thread. This will allow us to submit multiple calculations, wait for each one to complete, and then collect the results from our EndSimpleInterest() method. Using the APM model, we can use a callback to collect the results as well.

Performing calculations using APM

Now let’s review the same client code as before but this time we have updated it to use APM to perform the calculations in parallel. (Note that this code is taken from the Visual Studio solution at %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\2-After.)

public static void CalculateInParallel(List<double>
principalAmounts, BasicGridCalculator.BasicCalculator calculator)
{
         ...
            // Begin calculations in parallel
            List<IAsyncResult> results = new
            List<IAsyncResult>(principalAmounts.Count);
            foreach (double amount in principalAmounts)
            {
                IAsyncResult result =
                calculator.BeginSimpleInterest(amount, null, amount
                as Object);
                results.Add(result);
            }
            foreach (IAsyncResult result in results)
            {
                result.AsyncWaitHandle.WaitOne(); 
                double interest = calculator.EndSimpleInterest
                (result);
                double amount = (double)result.AsyncState;
                Console.WriteLine("Principal : ${0},\t Interest: 
                ${1}", amount, interest);
            }
}

To perform the calculations in parallel, first we need to set up a list of IAsyncResult objects to hold the results as they are collected asynchronously. The IAsyncResult objects store information about each interest calculation to be performed in the loop.. We will use this information to help us retrieve the results later in the second loop.

The BeginSimpleInterest() method takes three arguments: the principal amount, null (since we are not using any callback), and the principal amount typed as an object (we are using this as a convenient way to preserve the principal amount so we can retrieve it later when we print the output).

After calling BeginSimpleInterest(), the main thread continues execution while the asynchronous interest calculation takes place on the grid in the service process. Once executed, the BeginSimpleInterest() immediately returns a reference that is added to the results list so that we can keep track of the corresponding result. The main thread cycles through the loop repeating the same sequence for each principal amount.

Once all the asynchronous interest calculations are done, a second loop retrieves the results. When we reach result.AsyncWaitHandle.WaitOne(), the main thread is blocked until the specific result is available. We retrieve the result by passing the result reference to the EndSimpleInterest() method. We also retrieve the original principal amount from the AsyncState property of the result object.

Performing calculations using APM with callback

In this sample, we review how to perform the calculations in parallel but this time we collect the results using a callback to let us know when the results are ready. The following sample code is taken from the Visual Studio solution at %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\2-After.

public static void CalculateInParallelWithCallback(List<double>
principalAmounts, BasicGridCalculator.BasicCalculator calculator)
        {
            lock (resultLock)
            {
                outstandingMethodResults = principalAmounts.Count;
            }
            // Begin calculations in parallel (with a callback)
            foreach (double amount in principalAmounts)
            {
                SimpleInterestMethodContext ctx =
                new SimpleInterestMethodContext(calculator, amount);
                calculator.BeginSimpleInterest(amount,
                SimpleInterestCompleted, ctx as Object);
            }
            outstandingMethodsCompleted.WaitOne();
            outstandingMethodsCompleted.Reset();
        }

The first step is to initialize outstandingMethodResults to the number of expected results. We will use this variable to keep track of the number of results collected so that we know when all the work is done.

This time we construct a context object for each call in the loop since we will be processing the result in our callback method. As the callback will be called from a different thread, it is important to maintain a reference to our proxy so that we can still collect the results when the SimpleInterest() method completes later.

We pass three arguments to the BeginSimpleInterest() method: the principal amount, the delegate of the callback method, and the context object. When the call completes it will automatically call the callback that is supplied. At this point, the main thread waits until all the results are returned from the service.

Now let’s take a look at the callback. With an asynchronous client, when a calculation is completed by the service, there must be a means of communicating this status back to the client. The callback (or response handler) is implemented for this purpose. It is called by the Middleware each time the service completes a calculation. In the following sample, SimpleInterestCompleted() is the callback method. It accepts the result object, which is passed to the method by the middleware whenever the respective calculation has completed.

 private static void SimpleInterestCompleted(IAsyncResult result)
        {
            SimpleInterestMethodContext ctx =
            (SimpleInterestMethodContext)result.AsyncState;
            BasicGridCalculator.BasicCalculator calculator = ctx.obj;
            double amount = ctx.amount;
            try
            {
                double interest = calculator.EndSimpleInterest(result);
                Console.WriteLine("Principal : ${0},\t Interest: ${1}",
              amount, interest);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception Occured in
                SimpleInterestCompleted Callback : {0}", 
                ex.ToString());
            }
            finally
            {
                lock (resultLock)
                {
                    outstandingMethodResults--;
                    if (outstandingMethodResults < 1)
                    {
                        outstandingMethodsCompleted.Set();
                    }
                }
            }
        }

First, we restore the original context for the calculation. Next, we store a reference to the proxy on our result object so that we can make the call to calculator.EndSimpleInterest() when the callback is triggered from another thread. The result is retrieved by passing the result reference as a token to the EndSimpleInterest() method.

We count down the number of results outstanding. When all the results have returned, we set a flag, which unblocks the main thread in the CalculateInParallelWithCallback() method.

Step 8: Run the optimized sample on the grid

For instructions on how to run the optimized grid sample, refer to the Basic Calculator (After on-boarding) Readme located at %SOAM_HOME%\5.1\samples\AppOnboarding\DotNet\CS\BasicCalculator\2-After.