Introduction
This is a prototype / proof of concept project which shows how resources can be switched intuitively when some are not available. It was designed specifically for switching between N number of Automatic Identification System (AIS) live feeds - TCP Streams, however to easily implement this Demo we would not be using these live feeds instead we will be working with Files which would represent TCP streams.
Background
Please note that a working knowledge of AIS is not required to carry out this demo but if you would like some background information please follow these links.
AIS messages comes as a TCP stream from different AIS providers (http://www.aishub.net/,
http://marinetraffic.com/). The AIS message stores information for the ID of the vessel, the speed the destination and lots more, for more info please follow these links:
The Concept
The idea is that if a file exists, then this means that the service is available and vice versa. These services should also have a priority, this could be specified in a data structure. In this demo the files have also been prioritised by naming them in an ordered form from "TextFile0.txt" as the is highest priority to "TextFileN.txt" with lowest priority.
When "TextFile0.txt" is available, we do not need to use its next available service "ThreadFile1.txt" (i.e. the thread with the next priority). But if the "TextFile0.txt" is not available, then check the next and so on till the maximum possible service(File) in the collection is reached.
If currently we are using (the fourth service) "TextFile3.txt", then this means that there are three other services with higher priority which could probably be restored later on while we still use "TextFile3.txt". That's why we need to periodically check if the other services are available = if the other files exists.
Figure 1 - Simulating switching between N number of TCP Streams with Files.
Testing the Software
To test the software just run the application and begin to change the names of the files (check Files folder for project test files). When you change the file "TextFile0.txt", the application listens for the next service(file) "TextFile1.txt" You would notice that after some time there will be a check again if TextFile0.txt exist (if the service is restored).
Note that "App.config" - stores configuration of the number of services = files = threads
There is always one main thread - the main thread of the console application.
Points of Interest - Source Code Explanations
In order to check if pervious file / Thread exists I am using Timers. But because every timer correspond to a thread I have extended the Timer class, and added ID - which is the ID of the thread, as well as some addition fields (these were just for description purpose). public class ServiceTimer : Timer
public class ServiceTimer : Timer
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
Timers are managed by the "ServiceTimers.cs" class. Which contains a dictionary collection of Timers. A Tick method is used to specify a delegate which will process Elapsed event for a service with specific "id".
public class ServiceTimers
{
public delegate void OnElapsedTime(object sender, ElapsedEventArgs e);
public OnElapsedTime OnElapsed { get; set; }
private Dictionary<string, ServiceTimer> timers = new Dictionary<string, ServiceTimer>();
public void Add(ServiceTimer serviceTimer, OnElapsedTime onElapsed)
{
OnElapsed = onElapsed;
timers.Add(serviceTimer.Name, serviceTimer);
}
public void Tick(int id)
{
foreach (var t in timers.Values)
{
if (t.Id == id)
{
t.Elapsed += new ElapsedEventHandler(OnElapsed);
break;
}
}
}
}
Lets now have a look at the main class: "ActiveSwitcher.cs".
It has a couple of arrays with size equal to the size of the services - files available.
bool[] exit = new bool[ServicesCount];
- set to true only before stopping the threads.
bool[] works = new bool[ServicesCount];
- set true if the file - the service works correct and false if the file do not exists.
object[] lockers = new object[ServicesCount];
- collection of locking object - when we have to lock code in critical section.
CheckResource(int resourceID);
- this delegate is used because is better the check of the resource to be done outside the class. The idea is that we want to make the ActiveSwitcher work as a black box - just specify the number of services, a way to check them and timer intervals to check.
In the Main()
method of the application - in Program.cs, this delegate is assigned to Exist method - this method just checks if the file in the specified path exists.
In the Init()
method all of the above arrays are initialized. As it is commented in the code only the main thread is marked
works[0] = true;
. We need that to start the checking of the services.
private void Init()
{
for (int i = 0; i < ServicesCount; i++)
{
exit[i] = false;
lockers[i] = new object();
works[i] = (i == 0);
}
}
StartThreads()
: When we start a service we need to have the backup service id specified as well. The implementation is done in that way so when we reach the last possible service(file) we specify that the backup service is the one before. Other logic can be applied here if necessary.
private void StartThreads()
{
for (int i = 0; i < ServicesCount; i++)
{
if (i == ServicesCount - 1)
{
new Thread(Work).Start(new CurrentAndNextService(i, i - 1));
}
else
{
new Thread(Work).Start(new CurrentAndNextService(i, i + 1));
}
}
}
InitTimers()
: Initializes all the timers which correspond to threads. The main thread timer - with id=0 is set to 100 ms, all the others are set to 2000ms = 2seconds. After the initialization, Tick event is called for all the threads.
private void InitTimers()
{
for (var i = 0; i < ServicesCount; i++)
{
_testServiceTimers.Add(i == 0 ? new ServiceTimer(i, 100) :
new ServiceTimer(i, 2000), OnElapsedTime);
}
for (var i = 0; i < ServicesCount; i++)
{
_testServiceTimers.Tick(i);
}
}
StopExecutionOfAllLockers()
: Set the exit[]
value for all the threads to true - this will mean that the thread is ready to exit. In the simulation this
is done on the 9997 execution of the main thread. If this is a Windows Service Application we can call that when the user stop event is called.
public void StopExecutionOfAllLockers()
{
for (var i = 0; i < lockers.Length; i++)
{
lock (lockers[i])
{
exit[i] = true;
Monitor.Pulse(lockers[i]);
}
}
}
PulseFirstWorkingLocker()
: Release the thread which is marked to work, the one which has
works[i]=true
. The timer which changes the value of works to other threads - with lower priority in order to check if eventually the service is returned - works correctly (check
OnElapsedTime
event in ActiveSwitcher.cs).
public void PulseFirstWorkingLocker()
{
for (var i = 0; i < works.Length; i++)
{
lock (MainLocker)
{
if (works[i])
{
lock (lockers[i])
{
Monitor.Pulse(lockers[i]);
break;
}
}
}
}
}
The class CurrentAndNextService
as the name suggests contains of the ids of the current and the next service - this is used in the
StartThreads()
method.
public class CurrentAndNextService
{
public int CurrentServiceId { get; set; }
public int NextServiceId { get; set; }
public CurrentAndNextService(int currentServiceId, int nextServiceId)
{
CurrentServiceId = currentServiceId;
NextServiceId = nextServiceId;
}
}
I am passing the service id in the input parameter of "Work" method. This method is started as a new thread. That's why the id of the service is passed as an object. It seem that this is the only appropriate type when you create a new thread. Inside the Work method there is infinite loop which stops only when the thread is marked as exit[]= true; If not then it just prints to the console the id of the thread.
private void Work(object currentSercice)
{
var currentAndNext = (CurrentAndNextService)currentSercice;
lock (lockers[currentAndNext.CurrentServiceId])
{
while (true)
{
Monitor.Wait(lockers[currentAndNext.CurrentServiceId]);
if (!exit[currentAndNext.CurrentServiceId])
{
Console.Write((currentAndNext.CurrentServiceId).ToString(CultureInfo.InvariantCulture));
if (Check(currentAndNext.CurrentServiceId))
{
works[currentAndNext.CurrentServiceId] = true;
}
else
{
works[currentAndNext.CurrentServiceId] = false;
lock (lockers[currentAndNext.NextServiceId])
{
works[currentAndNext.NextServiceId] = true;
}
}
}
else
{
Console.WriteLine("\nlastly called t" + currentAndNext.CurrentServiceId + "\n");
break;
}
}
}
}
Evaluation of what has been done
Concurrency Visualizer is a part of the Visual Studio package.
For the purpose of the test I will make the application stop after 100 calls.
When the thread started - the first part of the simulation:
When the threads stop - the last part of the simulation:
What conclusions we can get from the visualization?
Most of the time is spent for synchronization: 95%. The sleep takes 5%. I guess that DirectX GPU Engine is about the console application. Looking at the
visualizations and the percentages I was not able to notice anything strange or unexpected. Here should be noted that the process switching is different when different files exists. There are much more checks if more of the services - files do not exists.