S
Sherif ElMetainy
Hello
I ran you test program and got the same result.
But when I modified your Equals method (see below) I got this message
Equals called with an object with value
System.Collections.Hashtable+bucket[]
0
This means that the problem is like stefan said, happens when an object is
removed whose hashcode happens to be zero.
You should fix your Equals method.
public override bool Equals(object obj)
{
SdtsMsgKey key = obj as SdtsMsgKey;
if(key == null)
{
if(obj == null)
Trace.WriteLine("Equals called with a null object");
else
Trace.WriteLine("Equals called with an object with value " +
obj.GetType().FullName);
Trace.WriteLine(this.GetHashCode().ToString());
return false;
}
return ((this.ClientID == key.ClientID) &&
(this.MsgReference == key.MsgReference));
}
I ran you test program and got the same result.
But when I modified your Equals method (see below) I got this message
Equals called with an object with value
System.Collections.Hashtable+bucket[]
0
This means that the problem is like stefan said, happens when an object is
removed whose hashcode happens to be zero.
You should fix your Equals method.
public override bool Equals(object obj)
{
SdtsMsgKey key = obj as SdtsMsgKey;
if(key == null)
{
if(obj == null)
Trace.WriteLine("Equals called with a null object");
else
Trace.WriteLine("Equals called with an object with value " +
obj.GetType().FullName);
Trace.WriteLine(this.GetHashCode().ToString());
return false;
}
return ((this.ClientID == key.ClientID) &&
(this.MsgReference == key.MsgReference));
}
Ken said:Hi Jon,
I have finally managed to re-produce the problem using my test program. I
paste the program below. Could you please take a look at it? It basically
creates two threads: one adding element to the common Hashtable, and the
other removing element from the Hashtable. The Hashtable is synchronized
using Hashtable.Synchronized(myHashtable). Initially I thought this was the
cause of the problem, but later after I change to use
lock(myHashtable.SyncRoot) before adding or removing element, the problem
still existed, so I suspect this is a bug with the .Net Framework. The
NullReferenceException will happen after the program runing for some time
(within one hour).
//Here is the test program
using System;
using System.Threading;
using System.Collections;
using System.Diagnostics;
namespace HashtableTest
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class HashtableTesting
{
static Hashtable PendingAckMsgs = null;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
TextWriterTraceListener myWriter = new
TextWriterTraceListener(System.Console.Out);
Trace.Listeners.Add(myWriter);
Hashtable pendingAckMsgsUnsync = new Hashtable();
PendingAckMsgs = Hashtable.Synchronized(pendingAckMsgsUnsync);
//
// TODO: Add code to start application here
//
Thread addingThread = new Thread(new ThreadStart(AddEle));
Thread removingThread = new Thread(new ThreadStart(RemoveEle));
addingThread.IsBackground = true;
removingThread.IsBackground = true;
addingThread.Start();
removingThread.Start();
Console.ReadLine();
}
static void AddEle()
{
Trace.WriteLine("Start of adding thread.");
try
{
byte refNum = 0;
long clientID = 0;
while (true)
{
SdtsMsgKey key = new SdtsMsgKey();
key.ClientID = clientID;
key.MsgReference = refNum;
PendingAckMsgs.Add(key, "object");
refNum++;
clientID++;
Thread.Sleep(500);
}
}
catch(Exception e)
{
Trace.WriteLine(e.ToString());
}
}
static void RemoveEle()
{
Trace.WriteLine("Start of removing thread.");
try
{
Thread.Sleep(500);
byte refNum = 0;
long clientID = 0;
while (true)
{
SdtsMsgKey key = new SdtsMsgKey();
key.ClientID = clientID;
key.MsgReference = refNum;
PendingAckMsgs.Remove(key);
refNum++;
clientID++;
Thread.Sleep(1000);
}
}
catch(Exception e)
{
Trace.WriteLine(e.ToString());
}
}
}
}
//Here is the self-implemented key class
using System;
using System.Text;
namespace HashtableTest
{
/// <summary>
/// Key for outgoing SDTS messages
/// </summary>
public class SdtsMsgKey
{
/// <summary>
/// Destination ISSI
/// </summary>
public long ClientID
{
get
{
return clientID;
}
set
{
clientID = value;
}
}
/// <summary>
/// Message reference is a unique identifier for the messages to
each destination
/// </summary>
public byte MsgReference
{
get
{
return msgReference;
}
set
{
msgReference = value;
}
}
/// <summary>
/// Serves as a hash function for a particular type,
/// suitable for use in hashing algorithms and data structures like
a hash table.
/// EXPLANATION:
/// ClientID 3 bytes (shift left 3 bytes x 8 bits) bitwise-OR
MsgReference 1 byte
/// Makes 4 bytes exactly an int.
/// </summary>
/// <returns>Hash code</returns>
public override int GetHashCode()
{
return ((int) (ClientID << 24) | (int) MsgReference);
}
/// <summary>
/// Determines whether the specified Object is equal to the current
Object.
/// Required for use in data structures like a hash table.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
SdtsMsgKey key = obj as SdtsMsgKey;
return ((this.ClientID == key.ClientID) &&
(this.MsgReference == key.MsgReference));
}
/// <summary>
/// Get the string representation of the object.
/// </summary>
/// <returns>String representation of the object</returns>
public override string ToString()
{
StringBuilder msgString = new StringBuilder();
msgString.AppendFormat("ClientID={0}\n", ClientID);
msgString.AppendFormat("MsgReference={0}\n", MsgReference);
return msgString.ToString();
}
private long clientID = 0;
private byte msgReference = 0;
}
}
Jon Skeet said:Hmm... that does seem very odd. I'll have a look at it more closely
tonight. Do you have a test program which demonstrates the problem
(even after several hours)? Anything to help reproduce it would be
good.