Multiple Tasks in parallel - One Section "one after another" - c#

I have several tasks running the same method in parallel. For a specific part of this method. I want the tasks to go one after another (not parallel).
Background: In the specific Part, I open another Form, where the User enters a Password. I Don't want the User to enter the password for every single tasks. The first one should get saved in a global string and used by the other tasks.
private void S7_GetCPU()
{
//Some Stuff
if (CPUPassword != null)
{
EncryptedString pwd = new EncryptedString(CPUPassword);
this.CPU.SetPassword(pwd);
}
if (!this.CPU.PasswordValid)
{
CPUPassword = ((MainForm)mainform)
.ShowInputMessage("Enter Password for CPU"), true);
EncryptedString pwd = new EncryptedString(CPUPassword);
this.CPU.SetPassword(pwd);
}
//More Parallel-Stuff
}
I tried to put this part into a Invoke. The Input-Message opens only one time. But all other tasks freeze at that point, even after the first task has left the .invoke
private void S7_GetCPU()
{
//Some Stuff
this.Invoke(new System.Action(() =>
{
if (CPUPassword != null)
{
EncryptedString pwd = new EncryptedString(CPUPassword);
this.CPU.SetPassword(pwd);
}
if (!this.CPU.PasswordValid)
{
CPUPassword = ((MainForm)mainform)
.ShowInputMessage("Enter Password for CPU"), true);
EncryptedString pwd = new EncryptedString(CPUPassword);
this.CPU.SetPassword(pwd);
}
}));
//More Parallel-Stuff
}

Solved it by: lock
private static readonly object PWLock = new object();
private void S7_GetCPU()
{
//Some Stuff
lock(PWLock)
{
if (CPUPassword != null)
{
EncryptedString pwd = new EncryptedString(CPUPassword);
this.CPU.SetPassword(pwd);
}
if (!this.CPU.PasswordValid)
{
CPUPassword = ((MainForm)mainform)
.ShowInputMessage("Enter Password for CPU"), true);
EncryptedString pwd = new EncryptedString(CPUPassword);
this.CPU.SetPassword(pwd);
}
}));
//More Parallel-Stuff
}

Related

Save user messages sent to bot and send finished form to other user

For days I've been trying to solve this problem with my Telegram Bot.
I'm trying to send the user some questions after he "/start"s the bot.
I want to capture all user answers and then send it to some user that I want to see user answers in one message.
I tried to do it by sending inline buttons and couldn't find the way to wait for the next message from the user. I tried to store the answers in a string array, and it doesn't work either.
And at the end of the question, I want to send all user answers to some userid/channel in one message with all user questions.
I use Telegram.Bot library.
Here is my code
static string gotName = "0";
static string gotAge = "0";
static string gotMessage = "0";
static string[] Forminfo = { gotName, gotAge, gotMessage };
async private void Bot_OnUpdate(object sender, Telegram.Bot.Args.UpdateEventArgs e)
{
if (e.Update.Type == UpdateType.Message && e.Update.Message.Text == "/start")
{
var streg = new InlineKeyboardMarkup(new[]
{
new [] // first row
{
InlineKeyboardButton.WithCallbackData("Next Step","start")
}
});
var update = e.Update.Message.Text;
var strmsg = "To Start The Register please send the bot your name and click on Next Step";
await bot.SendTextMessageAsync(e.Update.Message.Chat.Id, strmsg, ParseMode.Html, replyMarkup: streg);
var usermsg = await bot.GetUpdatesAsync();
Forminfo[0] = usermsg.ToString();
}
}
async private void Bot_OnCallbackQuery(object sender, Telegram.Bot.Args.CallbackQueryEventArgs e)
{
var streg1 = new InlineKeyboardMarkup(new[]
{
new [] // first row
{
InlineKeyboardButton.WithCallbackData("Next","start1")
}
});
if (Forminfo[0] != "0")
{
var startedmsg = "Hello " + Forminfo[0].ToString() + "\n" +
"Please Send us your Age and click Next";
try
{
await bot.SendTextMessageAsync(e.CallbackQuery.Message.Chat.Id, startedmsg, ParseMode.Html, replyMarkup: streg1);
}
catch(HttpRequestException ex)
{
await bot.SendTextMessageAsync(e.CallbackQuery.Message.Chat.Id, "To Many Request Please Try Later.", ParseMode.Html);
}
}
}
There are a couple things to fix:
If all you are handling inside your OnUpdate callback are messages, use OnMessage instead
You are using OnUpdate and then manually call GetUpdates. You can't mix multiple approaches of getting updates - the StartReceiving call already handles calling GetUpdates internally.
By having one string[] as the result, you are assuming only one user will use the bot at the same time. A better approach would be to use a Dictionary<userId, result>.
In your SendTextMessageAsync call, you don't have to set ParseMode if you are sending regular text
I don't see what you are using the buttons for if you're never checking whether the user clicked them
This is a code example that does what you want, but does not validate the input at all:
const long TargetChannelId = 123456;
static readonly ConcurrentDictionary<int, string[]> Answers = new ConcurrentDictionary<int, string[]>();
private static async void Bot_OnMessage(object sender, MessageEventArgs e)
{
Message message = e.Message;
int userId = message.From.Id;
if (message.Type == MessageType.Text)
{
if (Answers.TryGetValue(userId, out string[] answers))
{
if (answers[0] == null)
{
answers[0] = message.Text;
await Bot.SendTextMessageAsync(message.Chat, "Now send me your age");
}
else if (answers[1] == null)
{
answers[1] = message.Text;
await Bot.SendTextMessageAsync(message.Chat, "Now send me your message");
}
else
{
Answers.TryRemove(userId, out string[] _);
await Bot.SendTextMessageAsync(message.Chat, "Thank you, that's all I need from you");
string answersText = $"User {answers[0]}, aged {answers[1]} sent the following message:\n{message.Text}";
await Bot.SendTextMessageAsync(TargetChannelId, answersText);
}
}
else
{
Answers.TryAdd(userId, new string[2]);
await Bot.SendTextMessageAsync(message.Chat, "Please send me your name");
}
}
}

Nested Asynchronous function in Silverlight

I am trying to call a nested Asynchronous function but I am not getting the required data.
Since I am using a wcf service with Silverlight I can only use Asynchronous functions.
In my code I am saving a set of rows containing userdata. Before I save it I need to check the username is unique. Now I only need to find out the first one and then break out of loop and show a message to the user.for simplicity sake, I have stripped the function of all the extra data and this is how it looks
private void SaveUsers(bool CloseForm)
{
ObservableCollection<User> _UpdatedUsers = new ObservableCollection<User>();
DatabaseServiceLocal _dataService = new DatabaseServiceLocal(Database);
foreach (UserViewModel _User in _AllUsers)
{
//bool success = _dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
if (_User.Dirty && !_User.IsBlank)
{
_dataService.CheckIsUserNameUnique += (s, e) =>
{
if (e.IsUnique)
_UpdatedUsers.Add(_User.SaveAsUser());
else
{
_UpdatedUsers = new ObservableCollection<User>();
csaMessageBox.Show(string.Format("Username {0} is not allowed as it already exists in the system. Please choose a different username.", ""), null);
return;
}
};
_dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
}
_dataService.UpdateStaffAndUsersCompleted += (s, e) =>
{
BusyIndicator = false;
if (e.Success)
{
}
if (CloseForm)
ReturnToHomePage();
else
{
LoadUsers();
OnUsersSaved();
}
}
BusyIndicator = true;
BusyMessage = "Saving...";
_dataService.UpdateUsers(Database.CurrentProject.ProjectID, Database.CurrentClient.ClientID, _UpdatedUsers, _DeletedProjectUsers);
}
In this case I am trying to find if the username is unique,show user a message and return.
Obviously it's not as simple as that.I have tried a couple more different ways but it didn't work. How do I get this working?
I think you can make your life easier by adding a couple of helper functions. The first one is an asynchronous function that checks whether a user is unique. You may need to add some code to set tcs.SetException if there is an error.
private Task<bool> IsUserUniqueAsync(UserViewModel user, DatabaseServiceLocal dataService)
{
var tcs = new TaskCompletionSource<bool>();
dataService.CheckIsUserNameUnique += (s, e) =>
{
tcs.SetResult(e.IsUnique);
};
dataService.IsUserNameUnique(user.UserName, user.UserID, Database.CurrentClient.ClientID);
return tcs.Task;
}
The second one updates all the users asynchrnously
public Task<bool> UpdateUsersAsync(ObservableCollection<User> updatedUsers, DatabaseServiceLocal dataService)
{
var tcs = new TaskCompletionSource<bool>();
BusyIndicator = true;
BusyMessage = "Saving...";
dataService.UpdateStaffAndUsersCompleted += (s, e) =>
{
BusyIndicator = false;
tcs.SetResult(e.Success);
};
dataService.UpdateUsers(Database.CurrentProject.ProjectID, Database.CurrentClient.ClientID, updatedUsers, _DeletedProjectUsers);
return tcs.Task;
}
Then your SaveUsers method becomes a bit simpler.
private async void SaveUsers(bool CloseForm)
{
ObservableCollection<User> _UpdatedUsers = new ObservableCollection<User>();
DatabaseServiceLocal _dataService = new DatabaseServiceLocal(Database);
Dictionary<Task<bool>, User> tasks = new Dictionary<Task<bool>, User>();
// start all tasks in parallel
foreach (UserViewModel _User in _AllUsers)
{
if (_User.Dirty && !_User.IsBlank)
{
tasks.Add(IsUserUniqueAsync(_User, _dataService), _User);
}
}
// process each task as it completes
while(tasks.Count() > 0 )
{
var task = await Task.WhenAny(tasks.Keys.ToArray());
if(task.Result)
{
_UpdatedUsers.Add(_User.SaveAsUser());
}
else
{
MessageBox.Show(string.Format("Username {0} is not allowed as it already exists in the system. Please choose a different username.", ""), null);
return;
}
tasks.Remove(task);
}
if( await UpdateUsersAsync(_UpdatedUsers, _dataService))
{
if (CloseForm)
ReturnToHomePage();
else
{
LoadUsers();
OnUsersSaved();
}
}
}
Your code would more or less look like this.
ObservableCollection<User> _UpdatedUsers = new ObservableCollection<User>();
int _verifiedUsersCount = 0;
DatabaseServiceLocal _dataService = new DatabaseServiceLocal(Database);
//Verify unique users
private void SaveUsers(bool CloseForm)
{
_dataService.CheckIsUserNameUnique += CheckIsUserNameUnique;
foreach (UserViewModel _User in _AllUsers)
{
//bool success = _dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
if (_User.Dirty && !_User.IsBlank)
{
_dataService.IsUserNameUnique(_User.UserName, _User.UserID, Database.CurrentClient.ClientID);
}
}
}
//Store verified users to save
private void CheckIsUserNameUnique(object s, CheckIsUserNameUniqueEventArgs e)
{
if (e.IsUnique)
_UpdatedUsers.Add(_User.SaveAsUser());
else
{
csaMessageBox.Show(string.Format("Username {0} is not allowed as it already exists in the system. Please choose a different username.", ""), null);
}
verifiedUsersCount++;
//Call after all the users have been verified for uniqueness
if (_AllUsers.Count() == verifiedUsersCount)
{
OnUniqueUserVerifyComplete();
}
}
//Save verified users
private void OnUniqueUserVerifyComplete()
{
//No unique users
if (_UpdatedUsers.Count < 1) { return; }
_dataService.UpdateStaffAndUsersCompleted += (s, e) =>
{
BusyIndicator = false;
if (e.Success)
{
}
if (CloseForm)
ReturnToHomePage();
else
{
LoadUsers();
OnUsersSaved();
}
};
BusyIndicator = true;
BusyMessage = "Saving...";
_dataService.UpdateUsers(Database.CurrentProject.ProjectID, Database.CurrentClient.ClientID, _UpdatedUsers, _DeletedProjectUsers);
}

facebook desktop app C#

I am trying to build a desktop app to use facebook api and get data from friends.
Anyways I am stuck in the log in stage.
I have used some advice and made the log in to facebook with WebBrowser. It works great.
I am stuck at trying to make it give me status = Failed or success
I tried doing it like this at the end of the button_1 method
if (!w.DocumentText.Contains(#"<div class=""linkWrap noCount"">Messages</div>"))
{
w.Navigate(#"http://www.facebook.com/login.php");
MessageBox.Show("Login error. Wrong username or password!");
}
else
{
MessageBox.Show("Logged in successfully");
}
the < div class=""linkWrap noCount"">Messages< /div> is only shown while logged in so thats why I use it to see if a user is logged in
but the problem is it always gives me an error (wrong user and pass) becasue it reads it before the browser finishes to navigate to the page. I tried threads and thread sleep and even timers but it doesnt seem to work
an ideas?
here is the code:
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
thread.Start();
string email = textBox1.Text;
string password = textBox2.Text;
// create a new browser
WebBrowser w = new WebBrowser();
w.Dock = DockStyle.Fill;
this.Controls.Add(w); // you may add the controll to your windows forms if you want to see what is going on
// latter you may not chose to add the browser or you can even set it to invisible...
// navigate to facebook
w.Navigate(#"http://www.facebook.com/login.php");
// wait a little
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(10);
System.Windows.Forms.Application.DoEvents();
}
HtmlElement temp=null;
// while we find an element by id named email
while (temp == null)
{
temp = w.Document.GetElementById("email");
System.Threading.Thread.Sleep(10);
System.Windows.Forms.Application.DoEvents();
}
// once we find it place the value
temp.SetAttribute("value", email);
temp = null;
// wiat till element with id pass exists
while (temp == null)
{
temp = w.Document.GetElementById("pass");
System.Threading.Thread.Sleep(10);
System.Windows.Forms.Application.DoEvents();
}
// once it exist set its value equal to passowrd
temp.SetAttribute("value", password);
// if you already found the last fields the button should also be there...
var inputs = w.Document.GetElementsByTagName("input");
int counter = 0;
bool enableClick = false;
// iterate through all the inputs in the document
foreach (HtmlElement btn in inputs)
{
try
{
var att = btn.GetAttribute("tabindex");
var name = btn.GetAttribute("id");
if (enableClick)// button to submit always has a differnt id. it should be after password textbox
{
btn.InvokeMember("click");
counter++;
}
if (name.ToUpper().Contains("PASS") || att=="4")
{
enableClick = true; // button should be next to the password input
}
// try a max of 5 times
if (counter > 5)
{
break;
}
}
catch
{
}
}
}
Checkout the facebook-sharp SDK for Windows forms:
https://github.com/facebook-csharp-sdk/facebook-winforms
I recommend you use Facebook C# SDK. It uses the OAuth protocol, for user-authentication.
Down an code example how to get user friends with Facebook-C#-SDK:
using Facebook; //add reference to facebook dll for it work
declare the fields:
private FacebookOAuthResult result;
private FacebookOAuthClient OAuth;
and
private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
if (webBrowser1.Url.AbsolutePath == "/login.php")
{
// do login..
}
if (FacebookOAuthResult.TryParse(e.Url, out result))
{
if (result.IsSuccess)
{
FacebookClient fbClient = new FacebookClient(result.AccessToken);
dynamic friends = fbClient.Get("/me/friends"); //User friends
// do something..
}
else
{
string errorDescription = result.ErrorDescription;
string errorReason = result.ErrorReason;
string msg = String.Format("{0} ({1})", errorReason, errorDescription);
MessageBox.Show(msg, "User-authentication failed!");
}
}
}
and then for start user-authentication:
//..
OAuth = new FacebookOAuthClient();
OAuth.AppId = appId; // see link above,you can find how to get it
OAuth.AppSecret = appSecret; // see link above,you can find how to get it
Uri loginUrl = OAuth.GetLoginUrl(paramenters);
webBrowser1.Navigate(loginUrl.AbsoluteUri);

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.

Boolean value magically changed to false from ViewModel to View

I need to validate two password fields. If they match then the credential has to be validated in the validation viewmodel. I set a bool value validPassword in the validation viewmodel and need to refer it in the view. Then do something in the view according to validPassword value. However, validPassword always is false when I refer it in the view even though it is true in the viewmodel.
ViewModel:
internal static bool validPassword;
public static bool CheckCredentials(string username, string password, string domain)
{
string userPrincipalName = username + "#" + domain + ".com";
try
{
using(var context = new PrincipalContext(ContextType.Domain, domain))
{
validPassword = true;
return context.ValidateCredentials(userPrincipalName, password);
}
}
catch // a bogus domain causes an LDAP error
{
errorsForPassword.Add("Invalid Login!");
validPassword = false;
return false;
}
}
Code behind the view:
private void PwBox_OnKeyDown(object sender, RoutedEventArgs e)
{
System.Windows.Controls.ToolTip toolTip = new System.Windows.Controls.ToolTip();
//PasswordBox passwordBox = sender as PasswordBox;
passwordAgain = PasswordAgainBox.Password;
if(string.IsNullOrEmpty(passwordAgain) || !string.Equals(passwordAgain, MiscParameterViewModel.password))
{
PwBoxBorder.BorderBrush = new SolidColorBrush(Colors.Red);
MiscParameterViewModel.nextButtonIsEnabled = false;
if(string.IsNullOrEmpty(passwordAgain))
{
toolTip.Content = "Please enter the password again!";
ToolTipService.SetToolTip(PasswordAgainBox, toolTip);
}
else if(!string.Equals(passwordAgain, MiscParameterViewModel.password))
{
toolTip.Content = "Passwords don't match!";
ToolTipService.SetToolTip(PasswordAgainBox, toolTip);
}
}
else
{
//ToolTipService.SetToolTip(PasswordAgainBox, null);
//PwBoxBorder.BorderBrush = new SolidColorBrush(Colors.Transparent);
_validationViewModel.Authenticate();
if(!ValidationViewModel.validPassword)
{
toolTip.Content = "Invalid password!";
ToolTipService.SetToolTip(PasswordBox, toolTip);
ToolTipService.SetToolTip(PasswordAgainBox, toolTip);
PwBoxBorder.BorderBrush = new SolidColorBrush(Colors.Red);
PwBoxAgainBorder.BorderBrush = new SolidColorBrush(Colors.Red);
}
else
{
ToolTipService.SetToolTip(PasswordBox, null);
ToolTipService.SetToolTip(PasswordAgainBox, null);
PwBoxBorder.BorderBrush = new SolidColorBrush(Colors.Transparent);
PwBoxAgainBorder.BorderBrush = new SolidColorBrush(Colors.Transparent);
}
}
}
Authenticate is an async method in the viewmodel and it calls CheckCredentials method.
Here is the Authenticate method:
public async void Authenticate()
{
MiscParameterViewModel.nextButtonIsEnabled = false;
NotifyPropertyChanged("NextButtonIsEnabled");
const string propertyKey = "Password";
bool isValid = false;
/* Call service asynchronously */
if(MiscParameterViewModel.servServiceLoginType == ServiceLoginTypes.Windows)
{
if(errorKeys.ContainsKey(propertyKey))
{
errorsForPassword.Clear();
errorKeys.TryRemove(propertyKey, out errorsForPassword);
/* Raise event to tell WPF to execute the GetErrors method */
RaiseErrorsChanged(propertyKey);
}
//if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(passwordAgain))
if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
errorsForPassword.Add("Login is required!");
errorKeys.TryAdd(propertyKey, errorsForPassword);
isValid = false;
}
else
{
isValid = await Task<bool>.Run(() =>
{
return CheckCredentials(username, password, domain);
})
.ConfigureAwait(false);
}
}
What's happening is that your Authenticate method is executing CheckCredentials on another thread, then returning control to your view. What this means is that you will (sometimes) get to this line:
if(!ValidationViewModel.validPassword)
before CheckCredentials has been called. You're seeing false because that's the default value for booleans - it hasn't been set yet.
You could fix this in a couple of different ways. You could return a Task from your authenticate method, and then call .Wait() on the task before checking validPassword.
Or you could simply remove the async/await from your Authenticate method and make it a synchronous method. Which is right depends on the rest of your application.
Edit: Here's my attempt at your authenticate method. I had to guess on some of the functionality you want.
public async Task<bool> Authenticate()
{
MiscParameterViewModel.nextButtonIsEnabled = false;
NotifyPropertyChanged("NextButtonIsEnabled");
const string propertyKey = "Password";
/* Call service asynchronously */
if(MiscParameterViewModel.servServiceLoginType == ServiceLoginTypes.Windows)
{
if(errorKeys.ContainsKey(propertyKey))
{
errorsForPassword.Clear();
errorKeys.TryRemove(propertyKey, out errorsForPassword);
/* Raise event to tell WPF to execute the GetErrors method */
RaiseErrorsChanged(propertyKey);
}
//if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(passwordAgain))
if(string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
errorsForPassword.Add("Login is required!");
errorKeys.TryAdd(propertyKey, errorsForPassword);
return false;
}
else
{
return await Task<bool>.Factory.StartNew(() => CheckCredentials(username, password, domain));
}
}
return false;
}
Once you get the task back, you'll have to decide what to do with it. If you just call .Wait(), it will work, but you'll get the same problem you had where the GUI freezes while you're waiting.
You might want to use the .ContinueWith() method instead, which will be called once the task is complete, and then in there you can update your password box. You might need to marshall the changes back onto the GUI thread (ContinueWith will be on another thread) to set the password box's values - not sure without the complete solution. Hope that helps

Categories