In the following code I simulate work being done on different threads by
sleeping a couple methods for about 40 ms. However, some of these methods
that should finish in about 40 -80 ms take as long as 2300 ms to complete.
This is fairly rare, but the test code below will definitely show it.
Somehow, I must not have my design right.
The idea of this code is that I do two different types of processing (
1-starting and 2-ending) based on unique ID numbers. For example, for ID #1,
I can do the start process or the end process, and I can do them in any
order (start doesn't have to be called before end in a particular slice of
time). The restriction is that I can't begin one of these processes while
the other one is in progress. In that case I have to wait. But only the
thead handling ID #1 processing (and only for the one blocked method) should
wait. Any other processing for other ID numbers should be able to proceed
without regard for the fact that ID #1 has to wait for one of its processes
to complete.
The two processes are called by different threads and I don't create those
threads (ASP.NET does).
(In case anyone wants to know what the practical use of this code is, it is
used in an ASP.NET app to initialize a session and then clean up/terminate a
session. ASP.NET reuses session numbers, so I have been forced to be sure
that I am do not begin initializing a session if that same session number is
in the process of being ended. The inverse situation also applies. However,
please direct any comments on ASP.NET sessions or session ID reuse to
another newsgroup topic. Thanks.)
I appreciate anyone pointing out design or implementation problems with this
code in regard to threading.
You can build this code as a C# console app and run it. It has a hard-coded
parameter in Main() determining how long it will run (loop over the tests).
(I apologize for the messy Main method that is used to start the testing.)
using System;
using System.Threading;
using System.Collections;
namespace testing
{
/// <summary>
/// solution for cleaning up sessions where sessionID can be reused.
/// </summary>
public class SessionStarter
{
private const int waitTime = 4000;//used in TryEnter
private static bool keepTesting = true;
private static TimeSpan maxTime = TimeSpan.MinValue;
private static string maxMethodName = "none";
private class ActiveSession
{
public string SessionID;
public ActiveSession(string sessionID)
{
this.SessionID = sessionID;
}//ctor
}//struct ActiveSession
private static Hashtable sessionsBeingProcessed = new Hashtable();
private static void CheckForConflictingSession(string sessionID, out
ActiveSession mySession)
{
DateTime start = DateTime.Now;
if (Monitor.TryEnter(sessionsBeingProcessed, waitTime))
{
try
{
if (sessionsBeingProcessed.Contains(sessionID))
{
mySession = sessionsBeingProcessed[sessionID] as ActiveSession;
Console.WriteLine(">>>>>>>>> " + Thread.CurrentThread.Name + ": " +
sessionID + " is already being processed! <<<<<<<<<");
}
else
{
mySession = new ActiveSession(sessionID);
sessionsBeingProcessed.Add(sessionID, mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + "
added to sessionsBeingProcessed");
}
}
finally
{
Monitor.Exit(sessionsBeingProcessed);
}
}
else
{
keepTesting = false;
Exception e = new Exception(Thread.CurrentThread.Name + ": " +
"Exception: could not get lock on sessionsBeingProcessed for " + sessionID);
//log it
throw e;
}
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; maxMethodName =
"CheckForConflictingSession";}
Console.WriteLine(Thread.CurrentThread.Name + ":
CheckForConflictingSession time: " + ts.TotalMilliseconds);
}//CheckForConflictingSession
public static void StartSession(string sessionID, int workAmount)
{
DateTime start = DateTime.Now;
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering
StartSession method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpStartSession(sessionID, mySession, workAmount);
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; maxMethodName = "StartSession";}
Console.WriteLine(Thread.CurrentThread.Name + ": StartSession time: " +
ts.TotalMilliseconds);
}//StartSession
private static void HelpStartSession(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnter(mySession, waitTime);
if (entered2)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is OK
to be initialized.");
try
{
//do all work
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Starting session
" + sessionID + " initialization...");
Thread.Sleep(workAmount);
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Finished session
" + sessionID + " initialization...");
}
catch (Exception )
{
keepTesting = false;
Exception me = new Exception("Exception: Unable to complete session " +
sessionID + " initialization.");
//log it
throw me;
}
finally
{
//Monitor.Pulse(mySession);
sessionsBeingProcessed.Remove(sessionID);
Monitor.Exit(mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is
unlocked by StartSession");
}
}
else
{
keepTesting = false;
Exception me2 = new Exception("Timed out. Could not get Monitor to start
sessionID " + sessionID);
//log it
throw me2;
}
}//HelpStartSession
public static void EndSession(string sessionID, int workAmount)
{
DateTime start = DateTime.Now;
Console.WriteLine(Thread.CurrentThread.Name + ": " + "entering EndSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflictingSession(sessionID, out mySession);
HelpEndSession(sessionID, mySession, workAmount);
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; maxMethodName = "EndSession";}
Console.WriteLine(Thread.CurrentThread.Name + ": EndSession time: " +
ts.TotalMilliseconds);
}//EndSession
private static void HelpEndSession(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnter(mySession, waitTime);
if (entered2)
{
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is OK
to be terminated.");
try
{
//do all work
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Beginning session
" + sessionID + " termination...");
Thread.Sleep(workAmount);
Console.WriteLine(Thread.CurrentThread.Name + ": " + "Completed session
" + sessionID + " termination.");
}
catch (Exception e)
{
keepTesting = false;
//log it, etc.
throw e;
}
finally
{
//Monitor.Pulse(mySession);
sessionsBeingProcessed.Remove(sessionID);
Monitor.Exit(mySession);
Console.WriteLine(Thread.CurrentThread.Name + ": " + sessionID + " is
unlocked by EndSession");
}
}
else
{
keepTesting = false;
//log and throw exception
Exception ex = new Exception(Thread.CurrentThread.Name + ": " + "Timed
Out. Could not get Monitor to end sessionID " + sessionID);
//log
throw ex;
}
}//HelpEndSession
public static void Main(string[] args)
{
SessionStarter ss = new SessionStarter();
DateTime timeLimit = DateTime.Now.AddMinutes(120.5015);
Thread startSessionThread;
Thread endSessionThread;
Thread startSessionThread2;
Thread endSessionThread2;
Thread startSessionThread3;
Thread endSessionThread3;
Thread startSessionThread4;
Thread endSessionThread4;
Thread startSessionThread5;
Thread endSessionThread5;
Random r = new Random();
while (keepTesting && DateTime.Now < timeLimit)
{
startSessionThread = new Thread(
new ThreadStart(ss.Start));
startSessionThread.Name = "startThread1";
endSessionThread = new Thread(
new ThreadStart(ss.End));
endSessionThread.Name = "endThread1";
startSessionThread2 = new Thread(
new ThreadStart(ss.Start2));
startSessionThread2.Name = "startThread2";
endSessionThread2 = new Thread(
new ThreadStart(ss.End2));
endSessionThread2.Name = "endThread2";
startSessionThread3 = new Thread(
new ThreadStart(ss.Start3));
startSessionThread3.Name = "startThread3";
endSessionThread3 = new Thread(
new ThreadStart(ss.End3));
endSessionThread3.Name = "endThread3";
startSessionThread4 = new Thread(
new ThreadStart(ss.Start4));
startSessionThread4.Name = "startThread4";
endSessionThread4 = new Thread(
new ThreadStart(ss.End4));
endSessionThread4.Name = "endThread4";
startSessionThread5 = new Thread(
new ThreadStart(ss.Start5));
startSessionThread5.Name = "startThread5";
endSessionThread5 = new Thread(
new ThreadStart(ss.End5));
endSessionThread5.Name = "endThread5";
Thread[] threads = new Thread[10];
int[] ordering = new int[10];
try
{
for (int i = 0; i < 10; ++i)
{
ordering[i] = -1;
}
for (int i = 0; i < 10; ++i)
{
int count = 0;
while (count < 25)
{
int j = r.Next(10);
bool duplicate = false;
for (int k = 0; k < ordering.Length; k++)
{
if (ordering[k] == j)
{
duplicate = true;
break;
}
}
if (!duplicate)
{
ordering[i] = j;
break;
}
}
if (ordering[i] < 0)
{
throw new Exception("error in setting up test");
}
}
threads[ordering[0]] = startSessionThread;
threads[ordering[1]] = endSessionThread;
threads[ordering[2]] = endSessionThread2;
threads[ordering[3]] = startSessionThread2;
threads[ordering[4]] = endSessionThread3;
threads[ordering[5]] = startSessionThread3;
threads[ordering[6]] = endSessionThread4;
threads[ordering[7]] = startSessionThread4;
threads[ordering[8]] = endSessionThread5;
threads[ordering[9]] = startSessionThread5;
for (int i = 0; i < 10; ++i)
{
threads[i].Start();
}
}
catch (Exception e)
{
keepTesting = false;
Console.WriteLine(e.Message);
}
finally
{
startSessionThread.Join();
endSessionThread.Join();
startSessionThread2.Join();
endSessionThread2.Join();
startSessionThread3.Join();
endSessionThread3.Join();
startSessionThread4.Join();
endSessionThread4.Join();
startSessionThread5.Join();
endSessionThread5.Join();
Console.WriteLine();
Console.WriteLine(" --------------- repeating test ---------------");
}
}
Console.Write("Main is finished. (Max time = " +
maxTime.TotalMilliseconds + " from " + maxMethodName + ")");
Console.Read();
}
#region ThreadStart Delegates
public void Start()
{
StartSession("Betty", 41);
}
public void Start2()
{
StartSession("Boop", 41);
}
public void Start3()
{
StartSession("Spunky", 41);
}
public void Start4()
{
StartSession("Grampy", 41);
}
public void Start5()
{
StartSession("Freddy", 41);
}
public void End()
{
EndSession("Betty", 41);
}
public void End2()
{
EndSession("Boop", 41);
}
public void End3()
{
EndSession("Spunky", 41);
}
public void End4()
{
EndSession("Grampy", 41);
}
public void End5()
{
EndSession("Freddy", 41);
}
#endregion
}//class SessionStarter
}//namespace