How to correctly parse received serial data into lines? - c#

I'm creating a program which communicates with a serial device which is constantly sending data. I'm reading data from device every 100ms (using a timer). I use port.ReadExisting() to receive all currently available data from the device then I try split it into lines, because I need to check some of the received data and the best way is to check lines. The problem occurs when device sends data which doesn't end with "\r\n" or '\n'.
In a perfect situation port.ReadExisting() returns: "sampletext\r\nsomesampletext\nsampletext\r\n
But a problem occurs when there's no CR or LF character at the end:
First time port.ReadExisting() returns this: "text\nsamp"
Second time port.ReadExisting() returns this: letext\r\ntext\r\n"
End result should look like this:
text
sampletext
text
But what I get looks like this:
text
samp
letext
text
My code:
This is the timer which runs every 100ms:
private void CommandTimer_Tick(object sender, EventArgs e)
{
BackgroundWorker seriaDataWorker = new BackgroundWorker();
seriaDataWorker.DoWork += (obj, p) => PrintSerialData();
seriaDataWorker.RunWorkerAsync();
}
BackgroundWorker which gets called by the timer:
private void PrintSerialData()
{
try
{
if (RandomReboot)
{
RebootWatch.Start();
}
if (COMport.IsOpen)
{
if (COMport.BytesToRead != 0)
{
SerialPrint(COMport.ReadExisting());
}
}
}
catch (System.IO.IOException SerialException)
{
return;
}
}
Function which parses received data into lines:
private void SerialPrint(string data)
{
using (var buffer = new StringReader(data))
{
string line = "";
while((line = buffer.ReadLine()) != null)
{
if (CheckForAnsw)
{
ReceivedCommandData = line;
if (ReceivedCommandData.Contains(AnswExpected))
{
ReceivedAnsw = true;
ReceivedLine = ReceivedCommandData;
ReceivedCommandData = "";
}
}
this.Invoke(new MethodInvoker(delegate
{
AppendText(TextBox_System_Log, Color.Black, line + "\r\n");
}
));
}
}
}
I know that the problem is that buffer.ReadLine() treats remainder of the string which doesn't end with a CR or LF character as a seperate line but I don't know how to fix it.
I tried using port.ReadLine() in the past but it is way slower and causes problems for me when serial ports get disconnected etc.

I don't think there's an easy way to handle this with the StringReader. Instead, you can split the string yourself:
private static string _buffer = string.Empty;
private static void SerialPrint(string data)
{
// Append the new data to the leftover of the previous operation
data = _buffer + data;
int index = data.IndexOf('\n');
int start = 0;
while (index != -1)
{
var command = data.Substring(start, index - start);
ProcessCommand(command.TrimEnd('\r'));
start = index + 1;
index = data.IndexOf('\n', start);
}
// Store the leftover in the buffer
if (!data.EndsWith("\n"))
{
_buffer = data.Substring(start);
}
else
{
_buffer = string.Empty;
}
}
private static void ProcessCommand(string command)
{
Console.WriteLine(command);
}

You can use AnonymousPipes to transport and buffer the incoming data and read them as lines to output them to somewhere.
Here is a little example which creates a server and client pipe stream, then writes data to the server in one task (with some newline in the data) and reads the data in a different task per line and outputs them to the console.
public class Program
{
public static async Task Main()
{
(var writer, var reader) = CreatePipe();
using (writer)
using (reader)
{
var writerTask = Task.Run(async () =>
{
writer.AutoFlush = true;
writer.Write("?");
for (int i = 0; i < 100; i++)
{
if (i % 10 == 9)
{
await writer.WriteAsync("!");
await writer.WriteAsync(Environment.NewLine);
await writer.WriteAsync("?");
}
else
{
await writer.WriteAsync((i % 10).ToString());
}
await Task.Delay(100);
}
writer.Close();
});
var readerTask = Task.Run(async () =>
{
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
Console.WriteLine(line);
}
});
await Task.WhenAll(writerTask, readerTask);
}
}
public static (StreamWriter, StreamReader) CreatePipe()
{
var server = new AnonymousPipeServerStream(PipeDirection.Out);
var client = new AnonymousPipeClientStream(server.GetClientHandleAsString());
return
(
new StreamWriter(server, Encoding.UTF8),
new StreamReader(client, Encoding.UTF8)
);
}
}
Try to adapt this code to your use case and comment if there are difficulies.

Your issue with \r\n and \n can be covered by using Environment.NewLine. I'm not sure what AppendText does, but if you're using it to store the values, then you're overdoing it. What you need is to store all data first in a StringBuilder then process them, OR process each data and store them in managed type such as Array, to define each line separately. Only use the string in the presentation layer (if you have some GUI that you want the user to see the results).
So, what I suggest is to store the lines in StringBuilder Something like this :
private readonly StringBuilder _strDataBuilder = new StringBuilder();
private void PrintSerialData()
{
try
{
if (RandomReboot)
{
RebootWatch.Start();
}
if(COMport.IsOpen && COMport.BytesToRead != 0)
{
var data = COMport.ReadExisting();
if(!string.IsNullOrEmpty(data)) {
_strDataBuilder.Append(data);
}
}
}
catch (System.IO.IOException SerialException)
{
return;
}
}
private void SerialPrint()
{
var data = _strDataBuilder.ToString();
if(string.IsNullOrEmpty(data)) { return; }
var lines = data.Split(Environment.NewLine);
if(lines.Length == 0) { return; }
for(int x = 0; x < lines.Length; x++)
{
var line = lines[x];
if (CheckForAnsw)
{
ReceivedCommandData = line;
if (ReceivedCommandData.Contains(AnswExpected))
{
ReceivedAnsw = true;
ReceivedLine = ReceivedCommandData;
ReceivedCommandData = "";
}
}
this.Invoke(new MethodInvoker(delegate
{
AppendText(TextBox_System_Log, Color.Black, line + Environment.NewLine);
}
));
}
}
Storing them first would make things more maintainability and fixability when you want to add more processing steps or reuse the results.
Although the SerialPrint() is unnessary if you just re-print the data in the GUI. As the data already separated in lines. So, if you do
TextBox_System_Log.Text = _strDataBuilder.ToString();
Directly, would list them in lines in the default color. However, if you intended to split them to process each line separately (to validate for instance), then it would be okay.

You can try like below code:
public void DataReceivedSerialPort(object sender, SerialDataReceivedEventArgs e)
{
readExistingData = "";
SerialPort sp = (SerialPort)sender;
sp.ReadTimeout = 100;
do
{
readExistingData = "";
try
{
readExistingData = sp.ReadLine();
if (readExistingData == "")
{
readExistingData = sp.ReadLine();
}
dataReadFromSerialPort += readExistingData;
}
catch
{
try
{
readExistingData = sp.ReadExisting();
dataReadFromSerialPort += readExistingData + "\r\n";
}
catch { }
}
UI.insert_new_items_into_textBoxUARTLog(readExistingData);
} while (readExistingData != "");
}

Related

Telegram Flood prevention

There was such a problem: I ran the program many times and now it throws the following error FloodException: Flood prevention. Telegram now requires your program to do requests again only after 73611 seconds have passed (TimeToWait property). If you think the culprit of this problem may lie in TLSharp's implementation, open a Github issue please.
I attach the code below:
using System;
using System.Text;
using System.Windows.Forms;
using TeleSharp.TL;
using TeleSharp.TL.Messages;
using TLSharp.Core;
namespace tgBM
{
public partial class Form1: Form
{
string phone;
string code;
int n = 1;
StringBuilder sb = new StringBuilder ();
TelegramClient client = new TelegramClient (2646156, "08ec188e0bdee432e568120348f5f13a"); // create a client with parameters
public Form1 ()
{
InitializeComponent();
}
string str = "";
public async void authAsync()
{
var dialogs = (TLDialogs) await client.GetUserDialogsAsync();
foreach (var element in dialogs.Chats)
{
TLChat chat = element as TLChat;
if (element is TLChannel)
{
var offset = 0;
TLChannel channel = element as TLChannel;
if (channel.Title == "TOPLES")
{
TLChannel ch = element as TLChannel;
TLInputPeerChannel inputPeer = new TLInputPeerChannel() {ChannelId = ch.Id, AccessHash = (long) ch.AccessHash};
while (n! = 11)
{
try
{
TLChannelMessages res = await client.SendRequestAsync <TLChannelMessages>
(new TLRequestGetHistory() {Peer = inputPeer, Limit = 20, AddOffset = offset, OffsetId = 0});
var msgs = res.Messages;
if (res.Count> offset)
{
offset + = msgs.Count;
foreach (TLAbsMessage msg in msgs)
{
if (msg is TLMessage)
{
TLMessage message = msg as TLMessage;
str + = n.ToString () + "\ t" + message.Id + "\ t" + message.FromId + "\ t" + message.Message + Environment.NewLine;
}
if (msg is TLMessageService)
continue;
n ++;
}
}
else
break;
}
catch (Exception ex)
{
MessageBox.Show (ex.Message);
break;
}
}
}
}
textBox3.Text = str;
}
}
private async void button1_Click (object sender, EventArgs e)
{
phone = textBox1.Text;
await client.ConnectAsync (); // make a connection
var hash = await client.SendCodeRequestAsync(phone);
}
private async void button2_Click (object sender, EventArgs e)
{
code = textBox2.Text;
var user = await client.MakeAuthAsync(phone, await client.SendCodeRequestAsync(phone), code);
authAsync();
}
}
}
In a comment, you said are in a testing phase.
In this case, you should read https://core.telegram.org/api/auth#test-accounts
According to this page, there are 3 ways to perform tests while limiting the risk for FLOOD_WAIT errors:
Connect to the Test servers instead of the Production servers (seems not possible with TLSharp)
Use Test accounts with phone numbers 99966XYYYY (only available on Test servers)
Connect as a user with the phone number you use to create the API ID/Hash
I can do all that with WTelegramClient (TLSharp is no longer maintained and cannot connect to Telegram servers anymore)

Reading time from GPS device

I'm trying to read the time from GPS messages, this is the code:
public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = gpsPort.ReadExisting();
string[] strArr = data.Split('$');
for (int i = 0; i < strArr.Length; i++)
{
string strTemp = strArr[i];
string[] lineArr = strTemp.Split(',');
if (lineArr[0].Equals("GPGGA"))
{
try
{
this.Invoke(new MethodInvoker(delegate ()
{
string time = lineArr[1].Remove(6);
time = time.Insert(2, ":");
time = time.Insert(5, ":");
labelGPSTime.Text = time;
}));
}
catch (Exception ex)
{
string err = ex.Message;
}
}
}
}
My code manages to read the time when I just start the program, but not after.
If I use a breakpoint and walk through it step by step it works, but not when it runs normally.
I tried using the same code with a timer instead of SerialPort_DataReceived, but it cuts the messages in the middle.

ProgressDialog doesnt work perfectly

Hello i am getting zip file from server and i have written code for it.
I am showing progressdialog also
Here is code
public class DownloadImageFromUrl : AsyncTask<string, string, string>
{
private ProgressDialog pd;
private Context context;
public DownloadImageFromUrl(Context context)
{
this.context = context;
}
protected override void OnPreExecute()
{
pd = new ProgressDialog(context);
pd.SetMessage("Downloading file. Please wait...");
pd.Indeterminate = false;
pd.Max = 100;
pd.SetProgressStyle(ProgressDialogStyle.Horizontal);
pd.SetCancelable(true);
pd.Show();
base.OnPreExecute();
}
protected override void OnProgressUpdate(params string[] values)
{
base.OnProgressUpdate(values);
pd.SetProgressNumberFormat(values[0]);
Log.Verbose("values", "" + values[0]);
pd.Progress = int.Parse(values[0]);
}
protected override void OnPostExecute(string result)
{
pd.Dismiss();
}
protected override string RunInBackground(params string[] #params)
{
var storagePath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDownloads);
string filePath = System.IO.Path.Combine(storagePath.AbsolutePath, "testfile.zip");
int count;
try
{
URL url = new URL("http://shadikidukan.co.in/abhi/AAA.zip");
filename = Path.GetFileName(new Uri(url.ToString()).AbsolutePath);
System.Console.WriteLine("Abhijir" + filename);
URLConnection connection = url.OpenConnection();
connection.Connect();
int LengthOfFile = connection.ContentLength;//Here connection.ContentLength return -1
Log.Verbose("length", ""+LengthOfFile);
InputStream input = new BufferedInputStream(url.OpenStream(), 8192);
OutputStream output = new FileOutputStream(filePath);
byte[] data = new byte[1024];
long total = 0;
while ((count = input.Read(data)) != -1)
{
total += count;
PublishProgress("" + (int)((total / 100) / LengthOfFile));
output.Write(data, 0, count);
}
output.Flush();
output.Close();
input.Close();
}
catch (Exception e)
{
System.Console.WriteLine("Abhijir"+e.Message);
}
return null;
}
}
connection.ContentLength return -1
and when file is downloading it display negative values and progress is not incrementing..
in progressupdate method it always get negative values
protected override void OnProgressUpdate(params string[] values)
{
base.OnProgressUpdate(values);
pd.SetProgressNumberFormat(values[0]);
Log.Verbose("values", "" + values[0]);//values[0] has negative values
pd.Progress = int.Parse(values[0]);
}
how to overcome that problem and display proper value in progressdialog.
A content-length of -1 indicates that the header is missing or should be ignored. This usually means that the server is using chunked transfer encoding. Per design, you cannot know the total length of the file you're trying to download, and therefore can't display a progress indicator.
Try to download the file in a desktop browser, you'll see that the total length isn't displayed until the transfer has ended.

Background Worker Locking Main Thread - Windows Forms C#

I have a background worker that I use to create files in the background.
I had it working so that the files were created and the UI was still responsive.
I made some changes and now I can't figure out why the background worker is locking my main thread.
Here are my background worker methods. I don't have a progress changed event.
private void filecreator_bgw_DoWork(object sender, DoWorkEventArgs e)
{
if (filecreator_bgw.CancellationPending == true)
{
e.Cancel = true;
}
else
{
myManager.createFiles((SelectedFileTypes) e.Argument);
}
}
private void filecreator_bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
//status_label.Text = "Canceled!";
}
else if (e.Error != null)
{
//status_label.Text = "Error: " + e.Error.Message;
}
else
{
// Check the file manager object to see if the files were created successfully
status_label.Text = "COMPLETE";
file_statusLabel.Text = "Files Created: " + DateTime.Now.ToShortTimeString();
System.Threading.Thread.Sleep(5000);
status_label.Text = "Click Create Files to Begin";
createfiles_button.Enabled = true;
}
}
Here is the method to create the files.
public void createFiles(SelectedFileTypes x)
{
if (string.IsNullOrEmpty(Filename) || (x.isCSV == false && x.isTAB == false && x.isXML == false))
{
filesCreated = false;
return;
}
// Declare the streams and xml objects used to write to the output files
XDocument xmlFile;
StreamWriter swCSV;
StreamWriter swTAB;
CSVFilename = Path.GetDirectoryName(Filename) + Path.DirectorySeparatorChar.ToString() +
Path.GetFileNameWithoutExtension(Filename) + "CSV_TEST.csv";
swCSV = new StreamWriter(CSVFilename);
TABFilename = Path.GetDirectoryName(Filename) + Path.DirectorySeparatorChar.ToString() +
Path.GetFileNameWithoutExtension(Filename) + "TAB_TEST.csv";
swTAB = new StreamWriter(TABFilename);
XMLFilename = Path.GetDirectoryName(Filename) + Path.DirectorySeparatorChar.ToString() +
Path.GetFileNameWithoutExtension(Filename) + "XML_TEST.csv";
xmlFile = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XComment("Crosswalk"));
xmlFile.Add(new XElement("ACCOUNTS"));
// String array for use when creating xml nodes
string[] splits;
// String used to read in a line from the input file
string line = "";
// Use a try and catch block, if any errors are caught, return false
try
{
// Read each line in the file and write to the output files
using (StreamReader sr = new StreamReader(Filename))
{
int i = 0;
while ((line = sr.ReadLine()) != null)
{
if (x.isCSV)
{
swCSV.WriteLine(line.Replace(delim, ","));
}
if (x.isTAB)
{
swTAB.WriteLine(line.Replace(delim, "\t"));
}
if (x.isXML)
{
if (i <= 0)
{
i++;
continue;
}
splits = line.Split(new string[] { delim }, StringSplitOptions.RemoveEmptyEntries);
xmlFile.Root.Add(
new XElement("ACCOUNTS",
from s in header
select new XElement(s, splits[Array.IndexOf(header, header.Where(z => z.Equals(s, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault())])
)
);
}
}
// Dispose of all objects
swCSV.Close();
swCSV.Dispose();
swTAB.Close();
swTAB.Dispose();
if (x.isXML)
{
//xmlFile.Save(Path.GetFullPath(Filename) + Path.GetFileNameWithoutExtension(Filename) + "_TEST.xml");
xmlFile.Save(XMLFilename);
}
}
}
catch (Exception)
{
filesCreated = false;
return;
}
// Return true if file creation was successfull
filesCreated = true;
}
In the do work method, I build a simple struct to determine what output file types should be made and then I pass it to the method. If I comment out that call to create the files, the UI still does not respond.
In the create files method, I build out the files based on the input file that I am transforming. I do use a LINQ statement to help build out XML tags, but the arrays holding the tags values are small, 3-5 elements depending on the file chosen.
Is there a simple solution, or should I re-design the method. If I have to re-design, what are things I should keep in mind to avoid locking the main thread.
Thanks
Here is how I call the runworkerasync method:
private void createfiles_button_Click(object sender, EventArgs e)
{
SelectedFileTypes selVal = new SelectedFileTypes();
foreach (var structVal in outputformats_checkedListBox.CheckedItems)
{
if (structVal.ToString().Equals("CSV", StringComparison.InvariantCultureIgnoreCase))
selVal.isCSV = true;
if (structVal.ToString().Equals("TAB", StringComparison.InvariantCultureIgnoreCase))
selVal.isTAB = true;
if (structVal.ToString().Equals("XML", StringComparison.InvariantCultureIgnoreCase))
selVal.isXML = true;
}
// Call the FileManager object's create files method
createfiles_button.Enabled = false;
filecreator_bgw.RunWorkerAsync(selVal);
}
UPDATE:
I updated the call to start the worker and then the call to create the files using the argument passed into the worker.
You cannot interact with most UI controls directly from a BackgroundWorker. You need to access outputformats_checkedListBox.CheckedItems from the UI thread and pass the resulting SelectedFileTypes object into the BackgroundWorker as a parameter.
Also, pleas enote that your cancellation logic really didn't do much. In order for it to work well, you need to check CancellationPending throughout the process, not just when starting.
Here is a rough example of how you should start the worker:
private void StartWorker()
{
SelectedFileTypes selVal = new SelectedFileTypes();
foreach (var structVal in outputformats_checkedListBox.CheckedItems)
{
if (structVal.ToString().Equals("CSV", StringComparison.InvariantCultureIgnoreCase))
selVal.isCSV = true;
if (structVal.ToString().Equals("TAB", StringComparison.InvariantCultureIgnoreCase))
selVal.isTAB = true;
if (structVal.ToString().Equals("XML", StringComparison.InvariantCultureIgnoreCase))
selVal.isXML = true;
}
filecreator_bgw.RunWorkerAsync(selVal);
}
private void filecreator_bgw_DoWork(object sender, DoWorkEventArgs e)
{
SelectedFileTypes selVal = (SelectedFileTypes)e.Argument;
myManager.createFiles(selVal);
}

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