About
This project presents a simple Demo WCF Service and “Tester” Client Application demonstration that implements concurrency and instancing behaviors on a service with multiple client thread calls to a method on the service. The Demo Service is a standard template WCF service application hosted by the development IIS. The service features one simple method… a test method that simulates a long running process (it sleeps for 3 seconds). The client “tester” is a simple console application that creates multiple threads that access the service and report back on the results. The objective of this project was not to demo setup and hosting of a service, nor the client interface, but retrieve and display results of service behaviors with respect to multi-threaded access. Discussion regarding the hosting and setup of the simple IIS hosted service application will be skipped in this project article.
Architecture
The demo project consists of these simple component topics:
- WCF Service (Hosted by IIS) Application Project “ServiceConcurrencyDemo”
- ITestService – Interface of Service and Operational Contracts
- TestService – Implements the Service Interface
- config (Configuration for Service Hosted on IIS – Generic)
- Client “Tester to Service” Windows Console Application “MultiThreadClientTester”
- Service Reference Proxy (WSDL) to WCF Service
- Program – Creates multiple threads that call the service, records time spent in threads, and reports statistics on the final results in a console window.
Service Application
A service template application project (ServiceConcurrencyDemo) was added to my Visual Studio solution. The code is available on GitHub [here].
ITestService (Interface for Service)
This interface is a simple test service that simulates a long running process. The ServiceContract for the simple demo service contains only one operation contract. The method simulates a long running process on a service and returns the TotalMilliseconds (int) spent in the service method. The code is available on GitHub [here].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// <summary> /// Interface for Service /// A simple test service that simulates a long running process /// </summary> [ServiceContract] public interface ITestService { /// <summary> /// TestMethod /// Simulates a long running process on a service /// </summary> /// <returns>the TotalMilliseconds (int) spent in the service method</returns> [OperationContract] int TestMethod(); } // end of class |
TestService.svc.cs (Code that Implements the Service Interface)
The service implementation code has service-level attributes configured to control the concurrency and the instance behaviors of the service. This code implements the contracts for the ITestService interface. The code is available on GitHub [here].
There are four modes that were available for configuration on the service:
Concurrency: Multiple | Instance: PerCall (Not Relevant)- Concurrency: Multiple | Instance: Single*
Concurrency: Single | Instance: PerCall (Not Relevant)- Concurrency: Single | Instance: Single
* This is the current configuration on the code in the repo.
The definitions, results, and discussion of the service behaviors is discussed in more details in the results section of this article. The use of concurrency is related to the instancing mode. In PerCall instancing, concurrency is not relevant, because each client call to the service is processed by a new service instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/// <summary> /// TestService /// Implements the ITestService Interface /// A simple test service that simulates a long running process /// Average time in method will be 3 seconds but /// depending on the service configuration, the average /// total client call time (including time spent in service) /// varies based on the configuration. /// Test Modes /// Concurrency: Multiple | Instance: PerCall /// Concurrency: Multiple | Instance: Single /// Concurrency: Single | Instance: PerCall /// Concurrency: Single | Instance: Single /// </summary> [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)] public class TestService : ITestService |
Service Interface Implementation Methods
The service implementations are described below.
TestMethod
This is the only method available on the service. When a client calls this method, it simulates a long running process by sleeping for three seconds and then returns the time spent in the method (representing milliseconds) as an integer.
1 2 3 4 5 6 7 8 9 10 11 12 |
/// <summary> /// TestMethod /// Simulates a long running process on a service /// </summary> /// <returns>the TotalMilliseconds (int) spent in the service method</returns> public int TestMethod() { DateTime start = DateTime.Now; Thread.Sleep(3000); DateTime end = DateTime.Now; return (int)end.Subtract(start).TotalMilliseconds; } |
Client “Tester to Service” Windows Console Application
The client “tester to service” is a simple windows console application project (MultiThreadClientTester) in the same solution that connects to the “Demo Service” by use of a Service Reference proxy generated by the Add Service Reference Wizard. The client program will use this proxy to test the OperationContract or methods available in the ServiceContract and return the results to the user on the console window.
The project code is available on GitHub [here].
Features
The client application was meant to only demonstrate consumption of the Demo service, so this client has limited scope of features. Some of the client application features are:
Simple Design
The user interface is simplified to do basic demo, testing, and reporting of the multi-thread calls to the service with its configured behaviors.
Multi-threading
The program creates and runs threads while each one calls and waits on a call to the service in parallel with other threads. Each thread asynchronously updates its run time on the console window after it completes the service call. The program flow is actually asynchronous but asks the user to wait for the work to be complete before running the stats.
Stats
After the thread work is complete, the user presses enter to displays simple stats on the threads including the average, max, and min run-times of the threads.
Program Code
The code behind file is for the client tester and manages the application in one file. The code is available on GitHub [here].
Fields
Private fields allow the client to work with the following:
- Proxy for the Service Reference (Generated by the SVCUTIL Wizard)
- List of integers representing results of thread run-times
1 2 3 4 |
// Holds Proxy to Service private static TestServiceClient proxy = null; // Holds List of Total Thread Times private static List<int> threadTimes = new List<int>(); |
Methods
Main
This main method is the entry point of the client tester application. It first creates a proxy object (instance of the reference created by SVCUTIL), then creates and runs 5 threads in parallel. It then asks the user to wait until thread completion so they can see the individual results on the console window and then display the stats.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/// <summary> /// Main Method /// Main method for the program /// </summary> /// <param name="args">none used</param> static void Main(string[] args) { Console.WriteLine("Press <ENTER> to begin..."); Console.ReadLine(); int numThreads = 5; proxy = new TestServiceClient(); // Create and Run 5 threads in Parallel ParallelLoopResult result = Parallel.For(0, numThreads, x => { ServiceThread(x); } ); Console.WriteLine("Wait for all 5 threads to finish then.."); Console.WriteLine("Press <ENTER> to run statistics..."); Console.ReadLine(); PrintStatistics(); Console.WriteLine("Press <ENTER> to quit..."); Console.ReadLine(); } // end of main method |
ServiceThread
This is the main thread that implements the “async await” pattern of asynchronous processing of the thread. It also accesses the service using the async version of the generated proxy methods (proxy.TestMethodAsync()). It calls the service and awaits the completion, then adds its total thread time information to the list of thread times.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/// <summary> /// ServiceThread /// Aysynchronous method, each thread calls this method /// </summary> /// <param name="state">not used</param> private static async void ServiceThread(object state) { DateTime start = DateTime.Now; Console.ForegroundColor = ConsoleColor.Red; int id = (int)state; Console.WriteLine("Thread {0} starting...", id); // Thread waits for the the service call to finish. int svcTime = await proxy.TestMethodAsync(); DateTime end = DateTime.Now; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Thread {0} ended. Time in thread: {1:0} ms, Time in service call: {2} ms.", id, end.Subtract(start).TotalMilliseconds, svcTime); Console.ResetColor(); // Add this to the thread time list // So that statistics can be run later threadTimes.Add((int)end.Subtract(start).TotalMilliseconds); } |
Print Statistics
This method runs and prints basic statistics (average, maximum, minimum) on the List of thread times.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/// <summary> /// PrintStatistics /// Prints statistics for the multi-thread service call /// tests /// </summary> private static void PrintStatistics() { Console.WriteLine(); Console.WriteLine("Statistics..."); Console.WriteLine(new string('=', 50)); Console.WriteLine($"Average Thread Time: {threadTimes.Average()} ms"); Console.WriteLine($"Maximum Thread Time: {threadTimes.Max()} ms"); Console.WriteLine($"Minimum Thread Time: {threadTimes.Min()} ms"); Console.WriteLine(new string('=', 50)); } // end of method |
Results
Recall there were four modes available for the service configuration:
Concurrency: Multiple | Instance: PerCall (Not Relevant)- Concurrency: Multiple | Instance: Single*
Concurrency: Single | Instance: PerCall (Not Relevant)- Concurrency: Single | Instance: Single
* This is the current configuration on the code in the repo.
Definitions
Concurrency Multiple
Each service instance processes multiple client method calls concurrently. The service implementation must be thread-safe to use this concurrency mode. This is ideal for multiple threads accessing the service method at the same time, however, resources must be thread-safe. The use of concurrency is related to the instancing mode. In PerCall instancing, concurrency is not relevant, because each message is processed by a new service instance.
Concurrency Single
Each service instance processes one client call to a method at a single time. This is the default concurrency mode (if not manually configured with attributes). If there is a multi-threaded client having multiple threads call this service, then the client call thread requests will be processed in series. Each service caller/thread must wait until the service has completed the current caller it is processing. The use of concurrency is related to the instancing mode. In PerCall instancing, concurrency is not relevant, because each message is processed by a new service instance.
Instance Single
In this mode, only one service InstanceContext object is used for all incoming client calls and is not recycled after the calls. If a service object does not exist, a new one is created when the service is called for the first time.
Instance PerCall
In this mode, a new service InstanceContext object is created prior to and recycled subsequent to each client call.
Summary
Results of average thread performance for the four modes are summarized below
- Concurrency: Multiple | Instance: Single* | Fastest | 3417 (ms)
Concurrency: Multiple | Instance: PerCall (Not Relevant) | Medium | 3549 (ms)Concurrency: Single | Instance: PerCall (Not Relevant) | Medium | 3651 (ms)- Concurrency: Single | Instance: Single | Slowest | 9472 (ms)
* This is the current configuration on the code in the repo.
Parallel Thread Response Results
The user can take this demo project one step further and increase the number of client threads to run in parallel while the service is configured to run concurrency multiple on a single instance. This will truly test the service capability and performance. The diagram below shows the results of average thread performance times as the number of threads increased from 5 to 50, 100, and 500 respectfully.
Demo
The client console application calling the service configured in concurrency multiple mode with a single instance serving 5 threads.
Serving 500 simulataneous threads the results are:
The client console application calling the service configured in concurrency single (no multi-threading allowed) mode with a single instance serving 5 threads.
Code
The entire project code repository is available on GitHub [here].