Recipe 18.10. Granting Multiple Access to Resources with a SemaphoreProblemYou have a resource you want only a certain number of clients to access at a given time. SolutionUse a semaphore to enable resource-counted access to the resource. For example, if you have an Xbox and a copy of Halo2 (the resource) and a development staff eager to blow off some steam (the clients), you have to synchronize access to the Xbox. Since the Xbox has four controllers, up to four clients can be playing at any given time. The rules of the house are that when you die, you give up your controller. To accomplish this, create a class called Halo2Session with a Semaphore called _Xbox like this: public class Halo2Session { // A semaphore that simulates a limited resource pool // private static Semaphore _Xbox; // Player handles for Xbox private static string [] _handles = new string [9]{"Igor", "AxeMan", "Frosty", "Dr. Death", "HaPpyCaMpEr", "Executioner", "FragMan", "Beatdown", "Stoney" }; The _handles array is an array of player names that will be used. In order to get things rolling, you need to call the Play method, shown in Example 18-7, on the Halo2Session class. Example 18-7. Play method
The first thing the Play method does is to create a new semaphore that has a maximum resource count of 4 and a name of Xbox. This is the semaphore that will be used by all of the player threads to gain access to the game. A ManualResetEvent called GameOver is created to track when the game has ended. public class XboxPlayer { public class Data { public ManualResetEvent _GameOver; public string _handle; } //… more class } To simulate the nine developers, you create nine threads, each with its own XboxPlayer.Data class instance to contain the player name and a reference to the GameOver ManualResetEvent. The thread creation is using the new (in the .NET Framework Version 2.0) ParameterizedThreadStart delegate, which takes the method to execute on the new thread in the constructor, but also allows you to pass the data object directly to a new overload of the THRead.Start method. Once the players are in motion, the Xbox "initializes" and then calls Release on the semaphore to open four slots for player threads to grab onto, then waits until it detects that the game is over from the firing of the GameOver event. The players initialize on separate threads and run the JoinIn method, shown in Example 18-8. First they open the Xbox semaphore by name and get the data that was passed to the thread. Once they have the semaphore, they call WaitOne to queue up to play. Once the initial four slots are opened or another player "dies," then the call to WaitOne unblocks and the player "plays" for a random amount of time, then dies. Once the players are dead, they call Release on the semaphore to indicate their slot is now open. If the semaphore reaches its maximum resource count, the GameOver event is set. Example 18-8. JoinIn method
When the Play method is run, output similar to the following is generated: Igor is waiting to play! AxeMan is waiting to play! Frosty is waiting to play! Dr. Death is waiting to play! HaPpyCaMpEr is waiting to play! FragMan is waiting to play! Executioner is waiting to play! Stoney is waiting to play! Beatdown is waiting to play! Xbox initializing… Halo2 loaded & ready, allowing 4 players in now… Frosty has been chosen to play. Welcome to your doom Frosty. >:) HaPpyCaMpEr has been chosen to play. Welcome to your doom HaPpyCaMpEr. >:) FragMan has been chosen to play. Welcome to your doom FragMan. >:) Dr. Death has been chosen to play. Welcome to your doom Dr. Death. >:) Frosty has fallen to their death and gives way to another player. Executioner has been chosen to play. Welcome to your doom Executioner. >:) HaPpyCaMpEr has died of lead poisoning and gives way to another player. Beatdown has been chosen to play. Welcome to your doom Beatdown. >:) FragMan has shot their own foot and gives way to another player. AxeMan has been chosen to play. Welcome to your doom AxeMan. >:) Dr. Death has was captured and gives way to another player. Stoney has been chosen to play. Welcome to your doom Stoney. >:) Executioner has choked on a rocket and gives way to another player. Igor has been chosen to play. Welcome to your doom Igor. >:) Beatdown has fallen to their death and gives way to another player. AxeMan has died of lead poisoning and gives way to another player. Stoney has bought the farm and gives way to another player. Igor has choked on a rocket and gives way to another player. Thank you for playing, the game has ended. DiscussionSemaphores are a new piece of the Framework in 2.0 and a welcome one. They are primarily used for resource counting and are available cross-process when named (as they are based on the underlying kernel semaphore object). Cross-process may not sound too exciting to many .NET developers until they realize that cross-process also means cross-appdomain. If you are creating additional appdomains to perform work in, say, for instance, to hold assemblies you are loading dynamically that you don't want to stick around for the whole life of your main appdomain, the semaphore can help you keep track of how many are loaded at a time. Being able to control access up to a certain number of users can be useful in many scenarios (socket programming, custom thread pools, etc.). See AlsoSee the "Semaphore," "ManualResetEvent," and "ParameterizedThreadStart" topics in the MSDN documentation. |