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.Threadin g;
using System.Collecti ons;
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.MinVal ue;
private static string maxMethodName = "none";
private class ActiveSession
{
public string SessionID;
public ActiveSession(s tring sessionID)
{
this.SessionID = sessionID;
}//ctor
}//struct ActiveSession
private static Hashtable sessionsBeingPr ocessed = new Hashtable();
private static void CheckForConflic tingSession(str ing sessionID, out
ActiveSession mySession)
{
DateTime start = DateTime.Now;
if (Monitor.TryEnt er(sessionsBein gProcessed, waitTime))
{
try
{
if (sessionsBeingP rocessed.Contai ns(sessionID))
{
mySession = sessionsBeingPr ocessed[sessionID] as ActiveSession;
Console.WriteLi ne(">>>>>>>>> " + Thread.CurrentT hread.Name + ": " +
sessionID + " is already being processed! <<<<<<<<<");
}
else
{
mySession = new ActiveSession(s essionID);
sessionsBeingPr ocessed.Add(ses sionID, mySession);
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + sessionID + "
added to sessionsBeingPr ocessed");
}
}
finally
{
Monitor.Exit(se ssionsBeingProc essed);
}
}
else
{
keepTesting = false;
Exception e = new Exception(Threa d.CurrentThread .Name + ": " +
"Exception: could not get lock on sessionsBeingPr ocessed for " + sessionID);
//log it
throw e;
}
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; maxMethodName =
"CheckForConfli ctingSession";}
Console.WriteLi ne(Thread.Curre ntThread.Name + ":
CheckForConflic tingSession time: " + ts.TotalMillise conds);
}//CheckForConflic tingSession
public static void StartSession(st ring sessionID, int workAmount)
{
DateTime start = DateTime.Now;
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + "entering
StartSession method: " + sessionID);
ActiveSession mySession;
CheckForConflic tingSession(ses sionID, out mySession);
HelpStartSessio n(sessionID, mySession, workAmount);
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; maxMethodName = "StartSession"; }
Console.WriteLi ne(Thread.Curre ntThread.Name + ": StartSession time: " +
ts.TotalMillise conds);
}//StartSession
private static void HelpStartSessio n(string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnte r(mySession, waitTime);
if (entered2)
{
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + sessionID + " is OK
to be initialized.");
try
{
//do all work
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + "Starting session
" + sessionID + " initialization. ..");
Thread.Sleep(wo rkAmount);
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + "Finished session
" + sessionID + " initialization. ..");
}
catch (Exception )
{
keepTesting = false;
Exception me = new Exception("Exce ption: Unable to complete session " +
sessionID + " initialization. ");
//log it
throw me;
}
finally
{
//Monitor.Pulse(m ySession);
sessionsBeingPr ocessed.Remove( sessionID);
Monitor.Exit(my Session);
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + sessionID + " is
unlocked by StartSession");
}
}
else
{
keepTesting = false;
Exception me2 = new Exception("Time d out. Could not get Monitor to start
sessionID " + sessionID);
//log it
throw me2;
}
}//HelpStartSessio n
public static void EndSession(stri ng sessionID, int workAmount)
{
DateTime start = DateTime.Now;
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + "entering EndSession
method: " + sessionID);
ActiveSession mySession;
CheckForConflic tingSession(ses sionID, out mySession);
HelpEndSession( sessionID, mySession, workAmount);
TimeSpan ts = DateTime.Now - start;
if (ts > maxTime){ maxTime = ts; maxMethodName = "EndSession ";}
Console.WriteLi ne(Thread.Curre ntThread.Name + ": EndSession time: " +
ts.TotalMillise conds);
}//EndSession
private static void HelpEndSession( string sessionID, ActiveSession
mySession, int workAmount)
{
bool entered2 = Monitor.TryEnte r(mySession, waitTime);
if (entered2)
{
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + sessionID + " is OK
to be terminated.");
try
{
//do all work
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + "Beginning session
" + sessionID + " termination..." );
Thread.Sleep(wo rkAmount);
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + "Completed session
" + sessionID + " termination.");
}
catch (Exception e)
{
keepTesting = false;
//log it, etc.
throw e;
}
finally
{
//Monitor.Pulse(m ySession);
sessionsBeingPr ocessed.Remove( sessionID);
Monitor.Exit(my Session);
Console.WriteLi ne(Thread.Curre ntThread.Name + ": " + sessionID + " is
unlocked by EndSession");
}
}
else
{
keepTesting = false;
//log and throw exception
Exception ex = new Exception(Threa d.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.Ad dMinutes(120.50 15);
Thread startSessionThr ead;
Thread endSessionThrea d;
Thread startSessionThr ead2;
Thread endSessionThrea d2;
Thread startSessionThr ead3;
Thread endSessionThrea d3;
Thread startSessionThr ead4;
Thread endSessionThrea d4;
Thread startSessionThr ead5;
Thread endSessionThrea d5;
Random r = new Random();
while (keepTesting && DateTime.Now < timeLimit)
{
startSessionThr ead = new Thread(
new ThreadStart(ss. Start));
startSessionThr ead.Name = "startThrea d1";
endSessionThrea d = new Thread(
new ThreadStart(ss. End));
endSessionThrea d.Name = "endThread1 ";
startSessionThr ead2 = new Thread(
new ThreadStart(ss. Start2));
startSessionThr ead2.Name = "startThrea d2";
endSessionThrea d2 = new Thread(
new ThreadStart(ss. End2));
endSessionThrea d2.Name = "endThread2 ";
startSessionThr ead3 = new Thread(
new ThreadStart(ss. Start3));
startSessionThr ead3.Name = "startThrea d3";
endSessionThrea d3 = new Thread(
new ThreadStart(ss. End3));
endSessionThrea d3.Name = "endThread3 ";
startSessionThr ead4 = new Thread(
new ThreadStart(ss. Start4));
startSessionThr ead4.Name = "startThrea d4";
endSessionThrea d4 = new Thread(
new ThreadStart(ss. End4));
endSessionThrea d4.Name = "endThread4 ";
startSessionThr ead5 = new Thread(
new ThreadStart(ss. Start5));
startSessionThr ead5.Name = "startThrea d5";
endSessionThrea d5 = new Thread(
new ThreadStart(ss. End5));
endSessionThrea d5.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("erro r in setting up test");
}
}
threads[ordering[0]] = startSessionThr ead;
threads[ordering[1]] = endSessionThrea d;
threads[ordering[2]] = endSessionThrea d2;
threads[ordering[3]] = startSessionThr ead2;
threads[ordering[4]] = endSessionThrea d3;
threads[ordering[5]] = startSessionThr ead3;
threads[ordering[6]] = endSessionThrea d4;
threads[ordering[7]] = startSessionThr ead4;
threads[ordering[8]] = endSessionThrea d5;
threads[ordering[9]] = startSessionThr ead5;
for (int i = 0; i < 10; ++i)
{
threads[i].Start();
}
}
catch (Exception e)
{
keepTesting = false;
Console.WriteLi ne(e.Message);
}
finally
{
startSessionThr ead.Join();
endSessionThrea d.Join();
startSessionThr ead2.Join();
endSessionThrea d2.Join();
startSessionThr ead3.Join();
endSessionThrea d3.Join();
startSessionThr ead4.Join();
endSessionThrea d4.Join();
startSessionThr ead5.Join();
endSessionThrea d5.Join();
Console.WriteLi ne();
Console.WriteLi ne(" --------------- repeating test ---------------");
}
}
Console.Write(" Main is finished. (Max time = " +
maxTime.TotalMi lliseconds + " from " + maxMethodName + ")");
Console.Read();
}
#region ThreadStart Delegates
public void Start()
{
StartSession("B etty", 41);
}
public void Start2()
{
StartSession("B oop", 41);
}
public void Start3()
{
StartSession("S punky", 41);
}
public void Start4()
{
StartSession("G rampy", 41);
}
public void Start5()
{
StartSession("F reddy", 41);
}
public void End()
{
EndSession("Bet ty", 41);
}
public void End2()
{
EndSession("Boo p", 41);
}
public void End3()
{
EndSession("Spu nky", 41);
}
public void End4()
{
EndSession("Gra mpy", 41);
}
public void End5()
{
EndSession("Fre ddy", 41);
}
#endregion
}//class SessionStarter
}//namespace