Queue problems across multiple threads - c#

There are many questions and articles on the subject of using a .NET Queue properly within a multi threaded application, however I can't find subject on our specific problem.
We have a Windows Service that receives messages onto a queue via one thread and is then dequeued and processed within another.
We're using lock when queuing and dequeuing, and the service had run fine for around 2 years without any problems. One day we noticed that thousands of messages had been logged (and so had been queued) but were never dequeued/processed, they seem to have been skipped somehow, which shouldn't be possible for a queue.
We can't replicate the circumstances that caused it as we have no real idea what caused it considering that day was no different from any of the others as far as we're aware.
The only idea we have is to do with the concurrency of the queue. We're not using the ConcurrentQueue data-type, which we plan on using in the hope it is a remedy.
One idea, looking at the source of the Queue type, is that it uses arrays internally, which have to be resized once these buffers have reached a certain length. We hypothesised that when this is being done some of the messages were lost.
Another idea from our development manager is that using multiple threads on a multicore processor setup means that even though locks are used, the individual cores are working on the data in their local registers, which can cause them to be working on different data. He said they don't work on the same memory and seems to think lock only works as expected one a single core processor using multiple threads.
Reading more about ConcurrentQueue's use of volatile I'm not sure that this would help, as I've read that using lock provides a stronger guarantee of threads using the most up-to-date state of memory.
I don't have much knowledge on this specific subject, so my question is whether the manager's idea sounds plausible, and whether we might have missed something that's required for the queue to be used properly.
Code snippet for reference (forgive the messy code, it does need refactoring):
public sealed class Message
{
public void QueueMessage(long messageId, Message msg)
{
lock (_queueLock)
{
_queue.Enqueue(new QueuedMessage() { Id = messageId, Message = msg });
}
}
public static void QueueMessage(string queueProcessorName, long messageId, Message msg)
{
lock (_messageProcessors[queueProcessorName]._queueLock)
{
_messageProcessors[queueProcessorName].QueueMessage(messageId, msg);
_messageProcessors[queueProcessorName].WakeUp(); // Ensure the thread is awake
}
}
public void WakeUp()
{
lock(_monitor)
{
Monitor.Pulse(_monitor);
}
}
public void Process()
{
while (!_stop)
{
QueuedMessage currentMessage = null;
try
{
lock (_queueLock)
{
currentMessage = _queue.Dequeue();
}
}
catch(InvalidOperationException i)
{
// Nothing in the queue
}
while(currentMessage != null)
{
IContext context = new Context();
DAL.Message msg = null;
try
{
msg = context.Messages.SingleOrDefault(x => x.Id == currentMessage.Id);
}
catch (Exception e)
{
// TODO: Handle these exceptions better. Possible infinite loop.
continue; // Keep retrying until it works
}
if (msg == null) {
// TODO: Log missing message
continue;
}
try
{
msg.Status = DAL.Message.ProcessingState.Processing;
context.Commit();
}
catch (Exception e)
{
// TODO: Handle these exceptions better. Possible infinite loop.
continue; // Keep retrying until it works
}
bool result = false;
try {
Transformation.TransformManager mgr = Transformation.TransformManager.Instance();
Transformation.ITransform transform = mgr.GetTransform(currentMessage.Message.Type.Name, currentMessage.Message.Get("EVN:EventReasonCode"));
if (transform != null){
msg.BeginProcessing = DateTime.Now;
result = transform.Transform(currentMessage.Message);
msg.EndProcessing = DateTime.Now;
msg.Status = DAL.Message.ProcessingState.Complete;
}
else {
msg.Status = DAL.Message.ProcessingState.Failed;
}
context.Commit();
}
catch (Exception e)
{
try
{
context = new Context();
// TODO: Handle these exceptions better
Error err = context.Errors.Add(context.Errors.Create());
err.MessageId = currentMessage.Id;
if (currentMessage.Message != null)
{
err.EventReasonCode = currentMessage.Message.Get("EVN:EventReasonCode");
err.MessageType = currentMessage.Message.Type.Name;
}
else {
err.EventReasonCode = "Unknown";
err.MessageType = "Unknown";
}
StringBuilder sb = new StringBuilder("Exception occured\n");
int level = 0;
while (e != null && level < 10)
{
sb.Append("Message: ");
sb.Append(e.Message);
sb.Append("\nStack Trace: ");
sb.Append(e.StackTrace);
sb.Append("\n");
e = e.InnerException;
level++;
}
err.Text = sb.ToString();
}
catch (Exception ne) {
StringBuilder sb = new StringBuilder("Exception occured\n");
int level = 0;
while (ne != null && level < 10)
{
sb.Append("Message: ");
sb.Append(ne.Message);
sb.Append("\nStack Trace: ");
sb.Append(ne.StackTrace);
sb.Append("\n");
ne = ne.InnerException;
level++;
}
EventLog.WriteEntry("Service", sb.ToString(), EventLogEntryType.Error);
}
}
try
{
context.Commit();
lock (_queueLock)
{
currentMessage = _queue.Dequeue();
}
}
catch (InvalidOperationException e)
{
currentMessage = null; // No more messages in the queue
}
catch (Exception ne)
{
StringBuilder sb = new StringBuilder("Exception occured\n");
int level = 0;
while (ne != null && level < 10)
{
sb.Append("Message: ");
sb.Append(ne.Message);
sb.Append("\nStack Trace: ");
sb.Append(ne.StackTrace);
sb.Append("\n");
ne = ne.InnerException;
level++;
}
EventLog.WriteEntry("Service", sb.ToString(), EventLogEntryType.Error);
}
}
lock (_monitor)
{
if (_stop) break;
Monitor.Wait(_monitor, TimeSpan.FromMinutes(_pollingInterval));
if (_stop) break;
}
}
}
private object _monitor = new object();
private int _pollingInterval = 10;
private volatile bool _stop = false;
private object _queueLock = new object();
private Queue<QueuedMessage> _queue = new Queue<QueuedMessage>();
private static IDictionary<string, Message> _messageProcessors = new Dictionary<string, Message>();
}

so my question is whether the manager's idea sounds plausible
Uhm. No. If all those synchronization measures would only work on single core machines, the world would have ended in complete Chaos decades ago.
and whether we might have missed something that's required for the queue to be used properly.
As far as your description goes, you should be fine. I would look at how you found out that you have that problem. logs coming in but then vanishing without being properly dequeued, wouldn't that be the default case if I simply turned off the service or rebooted the machine? Are you sure you lost them while your application was actually running?

You declare the object to be used for the lock as private object.
If you try this:
class Program
{
static void Main(string[] args)
{
Test test1 = new Test();
Task Scan1 = Task.Run(() => test1.Run("1"));
Test test2 = new Test();
Task Scan2 = Task.Run(() => test2.Run("2"));
while(!Scan1.IsCompleted || !Scan2.IsCompleted)
{
Thread.Sleep(1000);
}
}
}
public class Test
{
private object _queueLock = new object();
public async Task Run(string val)
{
lock (_queueLock)
{
Console.WriteLine($"{val} locked");
Thread.Sleep(10000);
Console.WriteLine($"{val} unlocked");
}
}
}
You will notice that the code that lies under the lock is executed even if another thread is running inside.
But if you change
private object _queueLock = new object();
To
private static object _queueLock = new object();
It changes how your lock works.
Now, this being your issue depends on if you have multiple instances that class or everything is running withing that same class.

Related

How can I get rid of a memory leak in my Android app involving a BroadcastReceiver

Here is the code for my initializer:
public void Initialize(MainActivity activity)
{
context = activity;
store = activity.store;
gui = activity.gui;
store.Initialize(activity);
initialized = true; // note the this is initialized to TRUE
}
Here is the code for my class constructor:
public TextReceiver()
{
Android.Util.Log.Debug("DEBUG", "CREATED a receiver :" + counter.ToString());
toKill = counter++;
}
Here is the code for my work-around:
public void Refresh()
{
initialized = true;
store = context.store;
gui = context.gui;
}
Here is the code for my callback:
public override void OnReceive(Context contextPass, Intent intent)
{
Android.Util.Log.Debug("DEBUG", "receiving a text message: initialized - [" + initialized.ToString() + "]");
if (initialized == false) { Refresh(); } // this is my current work-around
// but I fear this leaks memory
if (intent.HasExtra("pdus") && (ContextCompat.CheckSelfPermission(context, Manifest.Permission.ReadSms) == Permission.Granted))
{
Bundle MessageInfo = intent.Extras;
Java.Lang.Object[] pdus = (Java.Lang.Object[])MessageInfo.Get("pdus");
SmsMessage[] messages = new SmsMessage[pdus.Length];
for (int i = 0; i < messages.Length; i++)
{
SmsMessage toSend = null;
// Verizon phones use the string "3gpp2"
// Some other carriers use the sting
// "3gpp". If the version is not correct,
// this section will throw an exeception
try { toSend = SmsMessage.CreateFromPdu((byte[])(pdus[i]), "3gpp2"); }
catch { toSend = SmsMessage.CreateFromPdu((byte[])(pdus[i]), "3gpp"); }
if (store.Push(toSend) == true) // checks for duplicates
{
gui.TextsReceived();
}
}
}
}
The log file is large, but here is the significant section:
11-13 14:10:50.984 Google Pixel 3 Error 26765 Bugle zdt: Jibe SDK Service not available. Is the Jibe SDK service running? Did you call connect() and wait for the notification before calling an API function?
Note that the bug report mentions a service which is not running:
Is the Jibe SDK service running?
Did you call connect() and wait for the notification before calling an API function?
Is this a result of the way I am using the callback? Am I calling the callback improperly somehow?
My work-around works, but I fear it leaks memory because it instantiates a new BroadcastReciever with each callback and doesn't ever free the instance as far as I can tell.
Some more details:

Managing multiple threads with Main thread

I'm initializing and starting five threads in my Testing Class:
[Test]
public void ReportGeneratorFiveThreadTest()
{
var threads = new List<ReportGeneratorThread>();
var logger = new Log4NetLogger(typeof(ReportGeneratorThreadTest));
for (var i = 0; i < 5; i++)
{
var estimatedReportSize = EstimatedReportSize.Normal;
var thread = new ReportGeneratorThread(logger, new ReportGenerator(20), estimatedReportSize, new ManualResetEvent(false));
thread.Name = string.Format("ReportGeneratorThread{0}", i);
threads.Add(thread);
}
threads.ForEach(t => t.Start());
}
And I'm starting all threads by calling following method in ReportGeneratorThread class:
public void Start()
{
this.running = true;
this.t = new Thread(this.GenerateReport());
this.t.SetApartmentState(ApartmentState.STA);
this.t.Start();
}
Which calls a GenerateReport() method in order to perform an operation:
public void GenerateReport()
{
var didwork = false;
try
{
didwork = this.reportGenerator.GenerateReport(this.estimatedReportSize);
}
catch (Exception e)
{
this.log.LogError(ReportGenerator.CorrelationIdForPickingReport, string.Format(CultureInfo.InvariantCulture, "System"), string.Format(CultureInfo.InvariantCulture, "Error during report generation."), 0, e);
this.doneEvent.Reset();
Debug.WriteLine("Thread is aborted !!!");
}
finally
{
if (!didwork)
{
Thread.Sleep(Settings.Default.ReportGenerationInterval);
}
}
}
My purpose is to inform my main thread once a thread among all five threads gets aborted (an exception is thrown in GenerateReport() method) and restart it in my main thread afterwards. I have tried using ManualResetEvent for that purpose, but it seems like it is not the proper class to use for this purpose. Any approaches ?.

Azure: How to Delete "DeadLettered" Messages from Service Bus queue

I would like to delete the Dead Lettered messages from the service bus queue. In particular, this value is called DeadLetterMessageCount and you can find out this by right-clicking the "Properties" of the SB queue in the Server Explorer of your project (in case of using a SB queue).
The reason I would like to do this is because I've set up an Autoscale of the cloud service. So, when the SB queue is quite big, it adds some more cores in order to proceed the messages faster (it enables more worker roles). I realized that when you set up the scaling depending on the number of messages in the queue, it counts the DeadLettered messages as well (messages that cannot be consumed).
So that's a waste of money, as more instances are enabled that are not needed.
Any queries, please let me know.
Thanks for your help
You read and delete messages from dead letter queue the same way you read from normal queues or subscriptions.
You can use this method to get the path of the queue: QueueClient.FormatDeadLetterPath(queuePath).
Also see this previous answer: How do I delete a DeadLetter message on an Azure Service Bus Topic
This is a code to delete a Dead-Letter messages from Queues.
public async void DeleteMessagesFromQueueAsync()
{
bool isDeadLetter=true;
long SequenceNumber = 12;
string queuePath='queue name';
string connectionString='connection string of ASB Namespace';
BrokeredMessage _srcMessage = null;
DeleteMessageResponse _msgDeletionStatus = new DeleteMessageResponse();
MessageReceiver fromQueueClient = null;
try
{
MessagingFactory factory = MessagingFactory.CreateFromConnectionString(connectionString);
string _fromEntityPath = !isDeadLetter ? queuePath : QueueClient.FormatDeadLetterPath(queuePath);
fromQueueClient = await factory.CreateMessageReceiverAsync(_fromEntityPath, ReceiveMode.PeekLock);
BrokeredMessage _message = await fromQueueClient.ReceiveAsync(SequenceNumber);
if (_message != null)
_srcMessage= _message;
if (_srcMessage != null )
{
await _srcMessage.CompleteAsync();
}
}
catch (Exception ex)
{
}
finally
{
if (fromQueueClient != null)
await fromQueueClient.CloseAsync();
}
}
You can use 'ReceiveAndDelete' mode and 'ReceiveBatchAsync' to delete quickly from DeadLetterQueue
private async void button1_Click(object sender, EventArgs e)
{
try
{
var DLQPath = "/$DeadLetterQueue"; ///**** Important - Pointing to DLQ'
var topicName = "message";
var sub = "message-subscription";
int batchSize = 100;
runProcess = true;
_subscriptionClient = SubscriptionClient.CreateFromConnectionString(connectionSt, topicName, sub + DLQPath, ReceiveMode.ReceiveAndDelete);
int cnt = 0;
do
{
var messages = await _subscriptionClient.ReceiveBatchAsync(batchSize);
var msgCount = messages.Count();
if (msgCount == 0)
{
break;
}
cnt += msgCount;
labelCount.Text = cnt.ToString();
}
while (runProcess);
_subscriptionClient.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.GetBaseException().Message);
return;
}
}

How to check if a thread is running in C #?

I created a thread in C # 4.0 and would like to know how do I check if it is running?
You can use Thread.IsAlive to check to see if a Thread is running.
That being said, if you're using C# 4, it's rarely a good idea to make "threads" manually. You should consider using the TPL and the Task/Task<T> class, as this provides a much cleaner model to attach work to run after the task completes, pull data out of the operation, etc.
I use Mutex to verify this. Sometimes just verify is Thread is alive with Thread.IsAlive is not safe if you are running on Background.
Try this:
private void btnDoSomething()
{
try
{
string nameThread = "testThreadDoSomething";
var newThread = new Thread(delegate() { this.DoSomething(nameThread); });
newThread.IsBackground = true;
newThread.Name = nameThread;
newThread.Start();
//Prevent optimization from setting the field before calling Start
Thread.MemoryBarrier();
}
catch (Exception ex)
{
}
}
public void DoSomething(string threadName)
{
bool ownsMutex;
using (Mutex mutex = new Mutex(true, threadName, out ownsMutex))
{
if (ownsMutex)
{
Thread.Sleep(300000); // 300 seconds
if (Monitor.TryEnter(this, 300))
{
try
{
// Your Source
}
catch (Exception e)
{
string mensagem = "Error : " + e.ToString();
}
finally
{
Monitor.Exit(this);
}
}
//mutex.ReleaseMutex();
}
}
}

C# - confused on lock

For my network based project I need to lock some codes to prevent simultaneous access.
This is my code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utility;
using DataBaseConnection;
using System.Net.Sockets;
using System.Data;
using System.IO;
namespace SunHavenClasses
{
public delegate void CtatReceiveDelegate(string message);
public class ServerHandlerClass
{
public event CtatReceiveDelegate OnChatDataReceive;
private Settings settings;
private DBCon con;
private Utility.Network.Server server;
private Dictionary<string, Socket> UsersOnline;
private Dictionary<string, int> unAuthenticatedIps;
private string pass = "logisoftlogicielbarundipankar";
public ServerHandlerClass(Settings s)
{
settings = s;
con = s.GetConnection();
server = new Utility.Network.Server(7777);
server.ClientConnectEventArise += OnUserConnect;//.OnClientConnect(OnUserConnect);
server.ClientDataReceiveEventArise += OnUsersDataReceive;
server.ClientDataSendEventArise += OnDataSendToUser;
server.ClientDisconnectEventArise += OnUserDisconnect;
server.OnBlockUser += OnUserBlocked;
UsersOnline = new Dictionary<string, Socket>();
unAuthenticatedIps = new Dictionary<string, int>();
}
private void OnUserConnect(Utility.Network.ServerEventArguments e)
{
Stream data = Utility.Serializing.Serialize(settings);
data = ZipNEncrypt.Zip(new string[] { "settings" }, new Stream[] { data }, pass);
server.Send(data, e.ClientSocket);
//MessageBox.Show(e.ClientSocket.RemoteEndPoint.ToString() + " is Connected!!");
}
private void OnUsersDataReceive(Utility.Network.ServerEventArguments e)
{
Dictionary<string, System.IO.Stream> data = ZipNEncrypt.Unzip(e.Data, pass);
User user;
try
{
user = (User)Serializing.Deserialize(data["user"]);
if (!UsersOnline.ContainsKey(user.GetUserId()))
{
server.BlockIp(e.ClientSocket);
return;
}
data.Remove("user");
}
catch (Exception)
{
bool passed = true;
foreach (string key in data.Keys)
{
if (key.Equals("LoggedIn")) break;
string[] str = key.Split('_');
if (str[0].Equals("GetData"))
{
string strr = (string)Serializing.Deserialize(data[key]);
if (strr.Contains("Users"))
{
string ip = e.ClientSocket.RemoteEndPoint.ToString().Split(':')[0];
/*CHANGE 1.2.10 00:14*/
lock (unAuthenticatedIps)
{
if (!unAuthenticatedIps.ContainsKey(ip))
{
unAuthenticatedIps.Add(ip, 1);
}
else unAuthenticatedIps[ip] += 1;
if (unAuthenticatedIps[ip] >= 11) passed = false;
}
/*CHANGE 1.2.10 00:14*/
break;
}
else passed = false;//server.AddBlockedIp(ip);
}
else passed = false;
}
if (!passed)
{
server.BlockIp(e.ClientSocket);
}
}
foreach (string key in data.Keys)
{
if (key.Equals("LoggedIn"))
{
try
{
User u = (User)Serializing.Deserialize(data["LoggedIn"]);
if (!UsersOnline.ContainsKey(u.GetUserId()))
{
if (User.ValidateUser(u.GetUserId(), u.GetPassword(), con))
{
/*CHANGE 1.2.10 00:14*/
lock (UsersOnline)
{
UsersOnline.Add(u.GetUserId(), e.ClientSocket);
string ip = e.ClientSocket.RemoteEndPoint.ToString().Split(':')[0];
Utility.Log.Write("UserLog.log", u.GetUserId() +
" Logged In From Ip " + ip);
}
/*CHANGE 1.2.10 00:14*/
}
else
{
server.BlockIp(e.ClientSocket);
return;
}
}
else
{
Stream tmpStream = Serializing.Serialize("Same User");
tmpStream = ZipNEncrypt.Zip(new string[] { key + "ERROR_SameUser" },
new Stream[] { tmpStream }, pass);
server.Send(tmpStream, e.ClientSocket);
return;
}
}
catch (Exception) { }
return;
}
else if (key.Equals("chat"))
{
string ip = e.ClientSocket.RemoteEndPoint.ToString().Split(':')[0];
string message = ip + " : "+ (string)Serializing.Deserialize(data[key]);
OnChatDataReceive(message);
return;
}
string[] str = key.Split('_');
Stream dataStream = null;
object obj = null;
try
{
if (str[0].StartsWith("Get"))
{
if (str[0].Equals("GetData"))
{
string query = (string)Serializing.Deserialize(data[key]);
obj = con.GetData(query);
}
else if (str[0].Equals("GetColumn"))
{
string query = (string)Serializing.Deserialize(data[key]);
string[] tmp = query.Split('%');
obj = con.GetColumn(tmp[0], tmp[1]);
}
else if (str[0].Equals("GetColumnDistrinctValue"))
{
string query = (string)Serializing.Deserialize(data[key]);
string[] tmp = query.Split('%');
obj = con.GetColumnDistrinctValue(tmp[0], tmp[1]);
}
}
else
{
lock (this)
{
if (str[0].Equals("ExecuteUpdate"))
{
if (str[1].Equals("Query"))
{
Query query = (Query)Serializing.Deserialize(data[key]);
obj = con.ExecuteUpdate(query);
}
else if (str[1].Equals("String"))
{
string query = (string)Serializing.Deserialize(data[key]);
obj = con.ExecuteUpdate(query);
}
}
else if (str[0].Equals("ExecuteBatchUpdate"))
{
if (str[1].Equals("Query"))
{
Query[] query = (Query[])Serializing.Deserialize(data[key]);
obj = con.ExecuteBatchUpdate(query);
}
else if (str[1].Equals("String"))
{
string[] query = (string[])Serializing.Deserialize(data[key]);
obj = con.ExecuteBatchUpdate(query);
}
}
else if (str[0].Equals("ExecutrInsert"))
{
Query query = (Query)Serializing.Deserialize(data[key]);
obj = con.ExecutrInsert(query);
}
}
}
dataStream = Serializing.Serialize(obj);
dataStream = ZipNEncrypt.Zip(new string[] { key },
new Stream[] { dataStream }, pass);
}
catch (Exception ex)
{
dataStream = Serializing.Serialize(ex.Message);
dataStream = ZipNEncrypt.Zip(new string[] { key + "_ERROR" },
new Stream[] { dataStream }, pass);
}
server.Send(dataStream, e.ClientSocket);
}
}
private void OnDataSendToUser(Utility.Network.ServerEventArguments e)
{
}
private void OnUserDisconnect(Utility.Network.ServerEventArguments e)
{
//System.Windows.Forms.MessageBox.Show("Disconnected");
string ip = e.ClientSocket.RemoteEndPoint.ToString().Split(':')[0];
/*CHANGE 1.2.10 00:14*/
lock (unAuthenticatedIps)
{
if (unAuthenticatedIps.ContainsKey(ip))
unAuthenticatedIps.Remove(ip);
}
lock (UsersOnline)
{
foreach (string key in UsersOnline.Keys)
if (UsersOnline[key].Equals(e.ClientSocket))
{
Utility.Log.Write("UserLog.log", key + " Logged Out From Ip " + ip);
UsersOnline.Remove(key);
break;
}
}
/*CHANGE 1.2.10 00:14*/
}
private void OnUserBlocked(Utility.Network.ServerEventArguments e)
{
string ip = e.ClientSocket.RemoteEndPoint.ToString().Split(':')[0];
Utility.Log.Write("UserLog.log", "Blocked For Illegal Access From Ip " + ip);
}
public void Send(Stream dataStream)
{
foreach (string key in UsersOnline.Keys)
{
try
{
server.Send(dataStream, UsersOnline[key]);
}
catch (Exception) { }
}
}
public void Send(Stream dataStream, Socket client)
{
try
{
server.Send(dataStream, client);
}
catch (Exception) { }
}
/*changed*/
public bool AddUser(string userId, Socket socket)
{
if (UsersOnline.ContainsKey(userId)) return false;
UsersOnline.Add(userId, null);
return true;
}
public void RemoveUser(string userId)
{
if (!UsersOnline.ContainsKey(userId) || UsersOnline[userId] != null) return;
UsersOnline.Remove(userId);
}
}
}
Now I am not sure that I am using lock correctly.Please Give me some advice.
Thanks.
I'm guessing you read a lot more than you write? If so, a ReaderWriterLockSlim may be more appropriate to reduce blocking (take read when you just want to check for the key, and write to manipulate the data).
By that I mean you could do double-checked locking with a read at first, then if it fails take a write lock, check again and add if necessary.
Also - the lock(this) is generally frowned upon; having a separate lock object is preferred.
Note that to be effective, all access must respect the lock; there are some places where UsersOnline is locked, and some places where it is accessed without a lock, for example; those second cases may explode in a gooey mess.
For example:
if (!UsersOnline.ContainsKey(u.GetUserId()))
{
if (User.ValidateUser(u.GetUserId(), u.GetPassword(), con))
{
/*CHANGE 1.2.10 00:14*/
lock (UsersOnline)
{
UsersOnline.Add(u.GetUserId(), e.ClientSocket);
In the above, if it is possible that two threads are looking at UsersOnline, then you've already failed by attempting ContainsKey without the lock. If another thread is mutating the state when you do this.... boom.
First of all, you code isn't thread safe at all. In your code you locks only modifying operations (Remove, Add), but also you should lock all access to shared fields. Actually this code not thread safe at all. I think that in this case ReaderWriterLockSlim - would be the best choise.
Second. lock(this) - very bed idea. You should use special objects for this.
Finally, I think your code is messy and hard to understand. Maybe your class solve many different tasks. Maybe you should extract some logic to separate classes (for example create guarded dictionaries as separate classes) or something else.
Sample of using ReaderWriterLockSlim:
someSharedResource;
someSharedResourceRWLock = new ReaderWriterLockSlim();
Some reading code:
try
{
someSharedResourceRWLock.EnterReadLock();
//access to someSharedResource for reading
}
finally
{
someSharedResourceRWLock.ExitReadLock();
}
Some writing code:
try
{
someSharedResourceRWLock.EnterWriteLock();
//access to someSharedResource for modifications
}
finally
{
someSharedResourceRWLock.ExitWriteLock();
}
You are using it correctly some of the time. unAuthenticatedIps is being protected correctly, but UsersOnline is not. Let's consider two parallel threads passing through your code:
Thread A Thread B
-------- --------
if (!UsersOnline.ContainsKey(u.GetUserId()) Statement's true Statement's true
{
lock (UsersOnline) Get lock Block
{
UsersOnline.Add UsersOnline.Add
} Release Lock
} Get lock
UsersOnline.Add
Release lock
Notice that both threads A and B modify the UsersOnline dictionary. This structure would properly protect that object:
if (!UsersOnline.ContainsKey(u.GetUserId()))
{
if (User.ValidateUser(u.GetUserId(), u.GetPassword(), con))
{
lock (UsersOnline)
{
// Note the additional check in case another thread
// added this already
if (!UsersOnline.ContainsKey(u.GetUserId())
{
UsersOnline.Add(u.GetUserId(), e.ClientSocket);
// ...
}
}
}
else
{
server.BlockIp(e.ClientSocket);
return;
}
}
As far as the last lock goes (lock (this)), I don't yet see why you would need it. str and obj are both local variables, so you shouldn't need to worry about them being modified by separate threads. And, as other have stated, locking this is not recommended.
The advice given so far has been great, but I have a design suggestion you may want to consider. Replace this:
private Dictionary<string, Socket> UsersOnline;
with a custom Thread Safe Dictionary
private ThreadSafeDictionary<string, Socket> UsersOnline
If its a fit for your needs, it would be a nice way to separate the business logic from the threading logic.

Categories