I am currently writing an IRC bot. I'd like to avoid excess flood, so I decided to create a message queue that would send the next message every X milliseconds, but my attempt failed. Line 43:
unset.Add((string)de.Key);
throws an OutOfMemory exception. I have absolutely no idea what I'm doing wrong.
Perhaps I should also explain the general idea behind such (possibly complicated) way of queuing.
Firstly, the main Hashtable queueht stores ConcurrentQueue<string> types, where targets for the messages serve as keys. I would like the bot to iterate through the hashtable, sending one message from each queue (and removing the key if the queue is emptied). I couldn't think of a suitable method to work on the hashtable itself, so I decided to create another queue, ConcurrentQueue<string> queue, which would store keys and their order of use when emptying the queue.
Assuming a hypothetical situation with several hundred items in a queue (which might be possible), any new request would be delayed by Lord knows how long (built-in delay between messages plus latency), so I have the method Add() rebuild queue. I create a deep copy of queueht (or so I hope) and generate a new queue based on this disposable copy, getting rid of it in the process.
I assume my train of thought and/or code to be horribly wrong, since I have nearly no experience with threading, collections more complicated than simple arrays and OOP habits/conventions whatsoever. I would really appreciate the solution to my problem with an explanation. Thanks in advance!
EDIT: Posting the entire class.
class SendQueue
{
Hashtable queueht;
ConcurrentQueue<string> queue;
Timer tim;
IRCBot host;
public SendQueue(IRCBot host)
{
this.host = host;
this.tim = new Timer();
this.tim.Elapsed += new ElapsedEventHandler(this.SendNewMsg);
this.queueht = new Hashtable();
this.queue = new ConcurrentQueue<string>();
}
public void Add(string target, string msg)
{
try
{
this.queueht.Add(target, new ConcurrentQueue<string>());
}
finally
{
((ConcurrentQueue<string>)this.queueht[target]).Enqueue(msg);
}
Hashtable ht = new Hashtable(queueht);
List<string> unset = new List<string>();
while (ht.Count > 0)
{
foreach (DictionaryEntry de in ht)
{
ConcurrentQueue<string> cq = (ConcurrentQueue<string>)de.Value;
string res;
if (cq.TryDequeue(out res))
this.queue.Enqueue((string)de.Key);
else
unset.Add((string)de.Key);
}
}
if (unset.Count > 0)
foreach (string item in unset)
ht.Remove(item);
}
private void SendNewMsg(object sender, ElapsedEventArgs e)
{
string target;
if (queue.TryDequeue(out target))
{
string message;
if (((ConcurrentQueue<string>)queueht[target]).TryDequeue(out message))
this.host.Say(target, message);
}
}
}
EDIT2: I am aware that while (ht.Count > 0) will be executed indefinitely. It's just a part leftover from previous version which looked like that:
while (ht.Count > 0)
{
foreach (DictionaryEntry de in ht)
{
ConcurrentQueue<string> cq = (ConcurrentQueue<string>)de.Value;
string res;
if (cq.TryDequeue(out res))
this.queue.Enqueue((string)de.Key);
else
ht.Remove((string)de.Key);
}
}
But the collection cannot be modified when it's evaluated (and I found that out the hard way), so it's no longer like that. I just forgot to change the condition for while.
I took liberty of trying TheThing's solution. While it seems to fulfil its purpose, it doesn't send any messages... Here's its final form:
class User
{
public User(string username)
{
this.Username = username;
this.RequestQueue = new Queue<string>();
}
public User(string username, string message)
: this(username)
{
this.RequestQueue.Enqueue(message);
}
public string Username { get; set; }
public Queue<string> RequestQueue { get; private set; }
}
class SendQueue
{
Timer tim;
IRCBot host;
public bool shouldRun = false;
public Dictionary<string, User> Users; //Dictionary of users currently being processed
public ConcurrentQueue<User> UserQueue; //List of order for which users should be processed
public SendQueue(IRCBot launcher)
{
this.Users = new Dictionary<string, User>();
this.UserQueue = new ConcurrentQueue<User>();
this.tim = new Timer(WorkerTick, null, Timeout.Infinite, 450);
this.host = launcher;
}
public void Add(string username, string request)
{
lock (this.UserQueue) //For threadsafety
{
if (this.Users.ContainsKey(username))
{
//The user is in the user list. That means he has previously sent request that are awaiting to be processed.
//As such, we can safely add his new message at the end of HIS request list.
this.Users[username].RequestQueue.Enqueue(request); //Add users new message at the end of the list
return;
}
//User is not in the user list. Means it's his first request. Create him in the user list and add his message
var user = new User(username, request);
this.Users.Add(username, user); //Create the user and his message
this.UserQueue.Enqueue(user); //Add the user to the last of the precessing users.
}
}
public void WorkerTick(object sender)
{
if (shouldRun)
{
//This tick runs every 400ms and processes next message to be sent.
lock (this.UserQueue) //For threadsafety
{
User user;
if (this.UserQueue.TryDequeue(out user)) //Pop the next user to be processed.
{
string message = user.RequestQueue.Dequeue(); //Pop his request
this.host.Say(user.Username, message);
if (user.RequestQueue.Count > 0) //If user has more messages waiting to be processed
{
this.UserQueue.Enqueue(user); //Add him at the end of the userqueue
}
else
{
this.Users.Remove(user.Username); //User has no more messages, we can safely remove him from the user list
}
}
}
}
}
}
I tried switching to ConcurrentQueue, which should work as well (though in a more thread-safe way, not that I know anything about thread safety). I also tried switching to System.Threading.Timer, but that doesn't help either. I've run out of ideas long ago.
EDIT: Being a complete and utter idiot, I didn't set the time for Timer to start. Changing the bool part to a Start() method that changes the timer's dueTime and interval made it work. Problem solved.
From what I can best understand, you want to be able to queue users in order and each of their request.
Meaning, if one user request like 1000 request, others can still send theirs and the bot serves 1 request from each user in a FIFO manner.
If so, then what you need is a manner, similar to this functionality:
class User
{
public User(string username)
{
this.Username = username;
this.RequestQueue = new Queue<string>();
}
public User(string username, string message)
: this(username)
{
this.RequestQueue.Enqueue(message);
}
public string Username { get; set; }
public Queue<string> RequestQueue { get; private set; }
}
///......................
public class MyClass
{
public MyClass()
{
this.Users = new Dictionary<string, User>();
this.UserQueue = new Queue<User>();
}
public Dictionary<string, User> Users; //Dictionary of users currently being processed
public Queue<User> UserQueue; //List of order for which users should be processed
public void OnMessageRecievedFromIrcChannel(string username, string request)
{
lock (this.UserQueue) //For threadsafety
{
if (this.Users.ContainsKey(username))
{
//The user is in the user list. That means he has previously sent request that are awaiting to be processed.
//As such, we can safely add his new message at the end of HIS request list.
this.Users[username].RequestQueue.Enqueue(request); //Add users new message at the end of the list
return;
}
//User is not in the user list. Means it's his first request. Create him in the user list and add his message
var user = new User(username, request);
this.Users.Add(username, user); //Create the user and his message
this.UserQueue.Enqueue(user); //Add the user to the last of the precessing users.
}
}
//**********************************
public void WorkerTick()
{
//This tick runs every 400ms and processes next message to be sent.
lock (this.UserQueue) //For threadsafety
{
var user = this.UserQueue.Dequeue(); //Pop the next user to be processed.
var message = user.RequestQueue.Dequeue(); //Pop his request
/////PROCESSING MESSAGE GOES HERE
if (user.RequestQueue.Count > 0) //If user has more messages waiting to be processed
{
this.UserQueue.Enqueue(user); //Add him at the end of the userqueue
}
else
{
this.Users.Remove(user.Username); //User has no more messages, we can safely remove him from the user list
}
}
}
}
Basically, we have a queue of users. We pop the next user, process his first request and add him to the end of the user list if he has more request waiting to be processed.
Hope this clears some functionality. For the record, the code above is more of a pseudocode than a functional code xD
From what I can see, you never escape from the while since you never remove items from the temporary hashtable ht until outside of it. Thus, the count will always be > 0.
Try this:
class User
{
public User(string username)
{
this.Username = username;
this.RequestQueue = new Queue<string>();
}
private static readonly TimeSpan _minPostThreshold = new TimeSpan(0,0,5); //five seconds
public void PostMessage(string message)
{
var lastMsgTime = _lastMessageTime;
_lastMessageTime = DateTime.Now;
if (lastMsgTime != default(DateTime))
{
if ((_lastMessageTime - lastMsgTime) < _minPostThreshold)
{
return;
}
}
_requestQueue.Enqueue(message);
}
public string NextMessage
{
get
{
if (!HasMessages)
{
return null;
}
return _requestQueue.Dequeue();
}
}
public bool HasMessages
{
get{return _requestQueue.Count > 0;}
}
public string Username { get; set; }
private Queue<string> _requestQueue { get; private set; }
private DateTime _lastMessageTime;
}
class SendQueue
{
Timer tim;
IRCBot host;
public bool shouldRun = false;
public Dictionary<string, User> Users; //Dictionary of users currently being processed
private Queue<User> _postQueue = new Queue<User>();
public SendQueue(IRCBot launcher)
{
this.Users = new Dictionary<string, User>();
this.tim = new Timer(WorkerTick, null, Timeout.Infinite, 450);
this.host = launcher;
}
public void Add(string username, string request)
{
User targetUser;
lock (Users) //For threadsafety
{
if (!Users.TryGetValue(username, out targetUser))
{
//User is not in the user list. Means it's his first request. Create him in the user list and add his message
targetUser = new User(username);
Users.Add(username, targetUser); //Create the user and his message
}
targetUser.PostMessage(request);
}
lock(_postQueue)
{
_postQueue.Enqueue(targetUser);
}
}
public void WorkerTick(object sender)
{
if (shouldRun)
{
User nextUser = null;
lock(_postQueue)
{
if (_postQueue.Count > 0)
{
nextUser = _PostQueue.Dequeue();
}
}
if (nextUser != null)
{
host.Say(nextUser.Username, nextUser.NextMessage);
}
}
}
}
UPDATE: changed after better understanding requirements.
This provides both per user flood control and overall throttling. It is also much simpler.
Note this was written on the fly and hasn't even been compiled, and there are probably some threading issues around User instances that need to be considered, but it should work.
Related
Simple implementation; single process writing (although multiple tasks may write asynchronously) single process reading.
Most of the time it seems to be working fine, but every once in a while we get a message where the Body size is reasonable, but if you look at it in the Computer Management tool, it's nothing but '0's. This causes the XmlMessageFormatter on the reader to fail.
We added code that'll let us handle poisoned messages better, but we need the messages, so that alone is not acceptable.
Object:
public class SubscriptionData
{
public Guid SubscriptionInstanceId { get; set; }
public SubscriptionEntityTypes SubscriptionEntityType { get; set; }
public List<int> Positions { get; set; }
public List<EventInformation> Events { get; set; }
public int SubscriptionId { get; set; }
public SubscriptionData() { }
public SubscriptionData(SubscriptionEntityTypes entityType, List<int> positions, List<EventInformation> events, int subscriptionId)
{
SubscriptionEntityType = entityType;
Positions = positions;
Events = events;
SubscriptionId = subscriptionId;
SubscriptionInstanceId = Guid.NewGuid();
}
public override string ToString()
{
return $"Entity Type: {SubscriptionEntityType}, Instance Id: {SubscriptionInstanceId}, Events: {string.Join("/", Events)}, SubsId: {SubscriptionId}";
}
}
Writer:
private static void ConstructMessageQueue()
{
_messageQueue = MessageQueue.Exists(Queue) ?
new MessageQueue(Queue) : MessageQueue.Create(Queue);
_messageQueue.Label = QueueName;
}
private static void EnqueueSubscriptionData(SubscriptionEntityTypes entityType, List<int> positions, List<EventInformation> events, int subscriptionId)
{
Task.Run(() =>
{
var subsData = new SubscriptionData(entityType, positions, events, subscriptionId);
_logger.Info(ErrorLevel.Normal, $"Enqueuing subscription: {subsData}");
_messageQueue.Send(subsData);
});
}
Reader:
private void HandleNotifications()
{
var mq = new MessageQueue(Queue);
mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(SubscriptionData) });
while (!_cancellationToken.IsCancellationRequested)
{
Message message = null;
try
{
message = mq.Peek(TimeSpan.FromSeconds(5));
if (message != null)
{
var subsData = message.Body as SubscriptionData;
if (subsData == null)
continue;
_logger.Info(ErrorLevel.Normal, $"Processing subscription: {subsData}");
// Process the notification here
mq.Receive();
}
}
catch (MessageQueueException t) when (t.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
{
_logger.Info(ErrorLevel.Normal, $"Message Queue Peek Timeout");
continue;
}
catch (MessageQueueException t)
{
_logger.Exception(t, "MessageQueueException while processing message queue for notifications");
throw;
}
catch (Exception t)
{
_logger.Exception(t, "Exception while processing message queue for notifications");
}
}
}
If you're curious, I'm told that we peek and only receive after success so that we don't lose the message, but in reading up on this to try and help my coworker it looks like there are transactions.
The bad messages we get look like this.
It seems that Send method is not thread safe, you should not share MessageQueue object (your _messageQueue static variable) between multiple threads. It's discussed here:
Is MSMQ thread safe?
I'm working on a messenger program and I have a timer which constantly deletes and adds new list box items so the list box flickers all the time. I'm trying to make the flickering stop. The reason I'm constantly deleting and adding new list box items is because if a friend logs in, it will change there status from offline to online.
Timer code:
private void Requests_Tick(object sender, EventArgs e)
{
LoadData();
}
LoadData() code:
FriendsLb.BeginUpdate();
_S = new Status();
Image Status = null;
FriendsLb.Items.Clear();
try
{
var query = from o in Globals.DB.Friends
where o.UserEmail == Properties.Settings.Default.Email
select new
{
FirstName = o.FirstName,
LastName = o.LastName,
Email = o.Email,
Status = o.Status,
Display = string.Format("{0} {1} - ({2})", o.FirstName, o.LastName, o.Email)
};
newFriendsLb.DataSource = query.ToList();
newFriendsLb.ClearSelected();
FriendsLb.DrawMode = DrawMode.OwnerDrawVariable;
foreach (object contact in query.ToList())
{
string details = contact.GetType().GetProperty("Display").GetValue(contact, null).ToString();
string email = contact.GetType().GetProperty("Email").GetValue(contact, null).ToString();
string status = _S.LoadStatus(email);
if (status == "Online")
{
Status = Properties.Resources.online;
}
else if (status == "Away")
{
Status = Properties.Resources.busy;
}
else if (status == "Busy")
{
Status = Properties.Resources.away;
}
else if (status == "Offline")
{
Status = Properties.Resources.offline;
}
FriendsLb.Items.Add(new Listbox(_A.LoadFriendAvatar(email), Status, details));
}
contact = query.ToList();
FriendsLb.MeasureItem += FriendsLb_MeasureItem;
FriendsLb.DrawItem += FriendsLb_DrawItem;
FriendsLb.EndUpdate();
Is there a way to update the current list box items constantly rather than constantly deleting and adding new ones?
Here's the GUI:
The are several ways to remove the flicker - all basically involve not completely repopulating the list each time. For this, you want to get the current status for the users and simply update the existing list.
In order for the control to see changes to the list items, rather than an anonymous type, you need a User class so that you can implement INotifyPropertyChanged. This "broadcasts" a notice that a property value has changed. You will also need to use a BindingList<T> so those messages get forwarded to the control. This will also allow additions/deletions from the list to be reflected.
You will also need a concrete way to find each user, so the class will need some sort of ID.
public enum UserStatus { Unknown, Online, Offline, Away, Busy }
class User : INotifyPropertyChanged
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Image StatusImage;
private UserStatus status = UserStatus.Unknown;
public UserStatus Status
{
get{return status;}
set{
if (value != status)
{
status=value;
PropertyChanged(this, new PropertyChangedEventArgs("Status"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return string.Format("{0}, {1}: {2}", LastName, FirstName, Status);
}
}
Then the collection:
private BindingList<User> Users;
private Image[] StatusImgs; // See notes
The BindingList is then used as the DataSource for the control:
Users = GetUserList();
// display the list contents in the listbox:
lbUsers.DataSource = Users;
timer1.Enabled = true;
Updating the user status just involves resetting the Status on each user which has changed. The BindingList<User> will then notify the control to update the display:
private void UpdateUserStatus()
{
// get current list of user and status
var newStatus = GetCurrentStatus();
User thisUser;
// find the changed user and update
foreach (User u in newStatus)
{
thisUser = Users.FirstOrDefault(q => q.Id == u.Id);
// ToDo: If null, there is a new user in the list: add them.
if (thisUser != null && thisUser.Status != u.Status)
{
thisUser.Status = u.Status;
thisUser.StatusImage = StatusImgs[(int)u.Status];
}
}
}
Results:
Note that there is a potential leak in your app. If you drill into the code to get an image from Resources you will see:
internal static System.Drawing.Bitmap ball_green {
get {
object obj = ResourceManager.GetObject("ball_green", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
GetObject() is creating a new object/image each time you call it, your code doesnt show the old one being Disposed() so, it is likely leaking resources.
Since each online user doesn't need their own unique instance (or a new one when the status changes), load them once into a List or array so they can be reused:
// storage:
private Image[] StatusImgs;
...
// populate:
StatusImgs = new Image[] {Resources.ball_black, Resources.ball_green,
Resources.ball_red, Resources.ball_yellow, Resources.ball_delete};
...
// usage:
thisUser.StatusImage = StatusImgs[(int)u.Status];
You could also change it so the User class updates that itself when the Status changes.
Finally, you might want to consider a simple UserControl for the UI rather than what appears to be an owner drawn Listbox.
If you don't want to change your code structure to eliminate the repeated Clear/Reload cycle, you should suspend UI drawing while you are rebuilding your list using;
using(var d = Dispatcher.DisableProcessing())
{
/* your work... */
}
As suggested here In WPF, what is the equivalent of Suspend/ResumeLayout() and BackgroundWorker() from Windows Forms
when i try to set a specific Item in dataGrid , it changes all other item's values to that same value. I'm not sure if it's a bug or i done something wrong. Here is my code:
(Datagrid is in another window (Main window), so i called a function in that window to edit the value)
private void AAbutton1_Click(object sender, RoutedEventArgs e)
{
Account selected = new Account();
if (textBox2.Text != null)
selected.username = textBox2.Text;
if (textBox12.Text != null)
selected.password = textBox12.Text;
if (locationTxtBox2.Text != null)
selected.location = locationTxtBox2.Text;
MainWindow.Instance.editAccount(selected);
MainWindow.Instance.updateData();
MainWindow.Instance.needsSave = true;
}
And here is the function in the main window:
public void editAccount(Account acc)
{
Account acc2;
Account selected = (Account)dataGrid.SelectedItem;
acc2 = Manager.accounts.ElementAt(Manager.accounts.FindIndex(a=> a == selected));
acc2.username = acc.username;
acc2.password = acc.password;
acc2.location = acc.location;
}
I really couldn't find a solution for this problem.
And here is the Account class in case you need it:
public class Account
{
public String username { get; set; }
public String password { get; set; }
public String location { get; set; }
public Account(String username,String password, String location)
{
this.username = username;
this.password = password;
this.location = location;
}
public Account()
{
}
}
Just to mention , i use Mahapps.metro controls.
I was right! I read your mind.
This isn't a WPF question, a binding question, or a DataGrid question. It's a "how do references work in C#?" question. It's a good question.
On file load, you start with a list of encrypted Accounts, but in decryption, you copy all the decrypted properties of each one of the accounts into the same instance of Account, and add that one instance multiple times to the list. The decrypted ones are all the same instance. You start off OK, but then you go off the rails in DecryptAccounts().
Here's the bug:
public static void DecryptAccounts()
{
// Hmmm. What's he planning to do with this?
Account holder = new Account(null, null, null);
accounts.Clear();
foreach (Account acc in Encryptedaccounts)
{
// HERE IT IS. This is the same instance of holder on every
// iteration. After file load, every Account in accounts is the
// same object as every other.
// You need to create a new Account object for each account.
holder.username = Decrypt(acc.username, user.Decryptedpassword);
holder.password = Decrypt(acc.password, user.Decryptedpassword);
holder.location = Decrypt(acc.location, user.Decryptedpassword);
accounts.Add(holder);
}
}
public static void LoadFromFile()
{
if (File.Exists(Path.Combine(appdata, folder, file)))
{
Encryptedaccounts = JsonConvert.DeserializeObject<List<Account>>(File.ReadAllText(Path.Combine(appdata, folder, file)));
}
DecryptAccounts();
}
Here's the fix
Manager.cs
public Account DecryptAccount(Account acc)
{
return new Account {
username = Decrypt(acc.username, user.Decryptedpassword),
password = Decrypt(acc.password, user.Decryptedpassword),
location = Decrypt(acc.location, user.Decryptedpassword)
};
}
public static void DecryptAccounts()
{
accounts.Clear();
foreach (Account acc in Encryptedaccounts)
{
accounts.Add(DecryptAccount(acc));
}
}
// You've got the same issue here
private static void EncryptAccounts()
{
Encryptedaccounts.Clear();
foreach (Account acc in accounts)
{
Encryptedaccounts.Add(EncryptAccount(acc));
}
}
public Account EncryptAccount(Account acc)
{
return new Account {
username = Encrypt(acc.username, user.Decryptedpassword),
password = Encrypt(acc.password, user.Decryptedpassword),
location = Encrypt(acc.location, user.Decryptedpassword)
};
}
Some other issues here. Not bugs, but life will be easier if you do stuff the "proper WPF way":
Manager.accounts should be of type ObservableCollection<Account>. Then it will automatically notify the DataGrid whenever you add or remove items from it and you won't have to do this updateData() thing to manually refresh the grid all the time.
Manager and Account both ought to implement INotifyPropertyChanged and fire notifications on their properties when their values change. In C#6, this is very simple:
using System.Runtime.CompilerServices;
using System.ComponentModel;
// ... snip ...
public event PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
Then your properties look like this:
private String _username = null;
public String username {
get { return _username; }
set {
if (value != _username)
{
_username = value;
OnPropertyChanged();
}
}
}
When they do that, anything you bind them to in the UI will be notified whenever you change the values. You'll be able to set properties on the selected grid item and the UI will update without any grid refresh or anything -- it'll just know. Very convenient.
I am working on a project that uses Threads. In some cases, I have these problems:
Here is some piece of my code :
List<EmailAddress> lstEmailAddress = new List<EmailAddress>();
private void TimerCheckInternetConnection_Tick(object sender, EventArgs e)
{
lock (TicketLock)
{
if (UtilityManager.CheckForInternetConnection())
{
if (ApplicationRunStatus == Enum_ApplicationRunStatus.UnknownDisconnect || ApplicationRunStatus == Enum_ApplicationRunStatus.IsReady)
{
// Connect
ThreadPool.QueueUserWorkItem((o) =>
{
for (int i = 0; i < lstEmailAddress.Count; i++)
{
lstEmailAddress[i].IsActive = lstEmailAddress[i].Login();
}
this.BeginInvoke(new Action(() =>
{
// some code
}));
});
}
}
}
}
and this is EmailAddress class :
class EmailAddress
{
private Imap4Client imap = new Imap4Client();
private object objectLock = new object();
public bool IsActive;
public string Address;
public string Password;
public string RecieveServerAddress;
public int RecieveServerPort;
public bool Login()
{
lock (objectLock)
{
try
{
imap.ConnectSsl(RecieveServerAddress, RecieveServerPort);
}
catch (Exception)
{
}
try
{
imap.Login(Address, Password);
return true;
}
catch (Exception)
{
return false;
}
}
}
}
And my problem is this:
When I want to use Login procedure that belongs to EmailAddress Class, it has some conflict. As you can see, I used Lock but any thing changed.
For more details:
If I have 3 items in lstEmailAddress , the Login procedure has to be called 3 times by this code. but every time, the login procedure will work on same username and password. So all my emails cannot login correctly.
If I remove threadpool, it will be ok.
Your code is very confusing:
If you add the lock in your code, it will run synchroniously, only one thread at the time, which will lead to performance loss.
If you queue work via QueueUserWorkItem - it will run in other thread, and not inside TicketLock
You should incapsulate locks inside your class, and should not lock entire logic in your program.
You start work for a loop variable i, which is being closured for it's last value, which lead for a problem you state in last sentence.
lock object in Email class isn't static so it's being created for each instance, and doesn't actually lock anithing.
As you are using Invoke method, your code is being started from UI, and you need to pass the synchronization context. I suggest you to use TPL code for this, and do not directly work with ThreadPool
So I suggest you this solution:
List<EmailAddress> lstEmailAddress = new List<EmailAddress>();
private void TimerCheckInternetConnection_Tick(object sender, EventArgs e)
{
// remove this lock as we have another in Email class
//lock (TicketLock)
if (UtilityManager.CheckForInternetConnection())
{
if (ApplicationRunStatus == Enum_ApplicationRunStatus.UnknownDisconnect
|| ApplicationRunStatus == Enum_ApplicationRunStatus.IsReady)
{
for (int i = 0; i < lstEmailAddress.Count; i++)
{
// use local variable to store index
int localIndex = i;
// Connect
ThreadPool.QueueUserWorkItem((o) =>
{
// if you add a lock here, this will run synchroniosly,
// and you aren't really need the ThreadPool
//lock (TicketLock)
lstEmailAddress[localIndex].IsActive = lstEmailAddress[localIndex].Login();
this.BeginInvoke(new Action(() =>
{
// some code
}));
});
}
}
}
}
class EmailAddress
{
// if you have to login only for one user simultaneosly
// use static variables here, other wise simply remove the lock as it is useless
private static Imap4Client imap;
private static object objectLock;
// static constructor for only one initialization for a static fields
static EmailAddress()
{
objectLock = new object();
imap = new Imap4Client();
}
public bool IsActive;
public string Address;
public string Password;
public string RecieveServerAddress;
public int RecieveServerPort;
public bool Login()
{
// aquire a static lock
lock (objectLock)
{
try
{
imap.ConnectSsl(RecieveServerAddress, RecieveServerPort);
}
catch (Exception)
{
// STORE THE EXCEPTION!!!
// return as you haven't connected
return false;
}
try
{
imap.Login(Address, Password);
return true;
}
catch (Exception)
{
// STORE THE EXCEPTION!!!
return false;
}
}
}
}
Change your Code as and try . you code is queing item from lstEmailAddress where it will always go and hit last item from the list. change your code to inquie each item in threadpool. that should fix. it.
for (int i = 0; i < lstEmailAddress.Count; i++)
{
ThreadPool.QueueUserWorkItem((o) =>
{
lstEmailAddress[i].IsActive = lstEmailAddress[i].Login();
}
}
I'm looking for resources, or anyone who have writting scheduler in WCF.
What I want to achive is basically what can you see in OGame, or Travian or doznes of other text browser based game.
Player click and send task to server to make building for him, or unit or something else.
From what I figured out I need to run some kind if scheduler on server that will gather all tasks, and will track them, until some perdiod of time will pass (2 mins, 10 mins, 3 days etc.), and after that period end service should call an action, like send data to database.
Well. I've been trying to make something very simply, that I can start from and ended with this:
public class BuildingScheduler
{
public int TaskID { get; set; }
public string UserName { get; set; }
public DateTime TaskEnd { get; set; }
public string BuildingName { get; set; }
public bool TaskDone { get; set; }
public DateTime RemainingTime { get; set; }
TestDBModelContainer _ctx;
TestData _testData;
public IDuplexClient Client { get; set; }
public BuildingScheduler()
{
TaskDone = false;
}
public void MakeBuilding()
{
while (DateTime.Now <= TaskEnd)
{
//Client.DisplayMessage(DateTime.Now.ToString());
RemainingTime = DateTime.Now;
}
_testData = new TestData { DataName = BuildingName, Created = DateTime.Now };
_ctx = new TestDBModelContainer();
_ctx.TestDataSet.AddObject(_testData);
_ctx.SaveChanges();
TaskDone = true;
//Client.DisplayMessage("Building completed!");
}
}
static List<UserChannel> _userChannels = new List<UserChannel>();
static List<BuildingScheduler> _buildingSchedules = new List<BuildingScheduler>();
List<BuildingScheduler> _buildingSchedulesToRemove = new List<BuildingScheduler>();
[OperationContract]
public void DoWork()
{
IDuplexClient client = OperationContext.Current.GetCallbackChannel<IDuplexClient>();
UserChannel userChannel = new UserChannel { Client = client, ClientName = "TestClient" };
string clientName = (from p in _userChannels
where p.ClientName == "TestClient"
select p.ClientName).FirstOrDefault();
lock (((ICollection)_userChannels).SyncRoot)
{
if (clientName == null)
{
_userChannels.Add(userChannel);
}
}
CheckBuilding();
}
private void CheckBuilding()
{
BuildingScheduler bs = (from p in _buildingSchedules
where p.UserName == "TestClient"
select p).FirstOrDefault();
IDuplexClient client = (from p in _userChannels
where p.ClientName == "TestClient"
select p.Client).FirstOrDefault();
if (bs != null)
{
client.DisplayMessage(bs.RemainingTime);
}
}
private void StartBuilding()
{
foreach (BuildingScheduler bs in _buildingSchedules)
{
if (bs.TaskDone == false)
{
bs.MakeBuilding();
}
else if (bs.TaskDone == true)
{
_buildingSchedulesToRemove.Add(bs);
}
}
for(int i = 0; i <= _buildingSchedulesToRemove.Count; i++)
{
BuildingScheduler bs = _buildingSchedulesToRemove.Where(p => p.TaskDone == true).Select(x => x).FirstOrDefault();
_buildingSchedules.Remove(bs);
_buildingSchedulesToRemove.Remove(bs);
}
CheckBuilding();
}
[OperationContract]
public void MakeBuilding(string name)
{
BuildingScheduler _buildng = new BuildingScheduler();
//_buildng.Client = client;
_buildng.TaskID = 1;
_buildng.UserName = "TestClient";
_buildng.TaskEnd = DateTime.Now.AddSeconds(50);
_buildng.BuildingName = name;
_buildingSchedules.Add(_buildng);
StartBuilding();
}
I have hardcoded most values, for testing.
InstanceContextMode is set for PerCall.
Anyway. This code is working. At least to some point. If we ignore zylion exceptions from Entity Framework, I can add tasks from multiple clients and they are added to db in order from newset to oldest (or shortest to longest ?).
The point is, user CANT track his tasks. When I change page I don't see how much time remaining till task done. This code essentialy do not provide time tracking because i got rid of it as it wasn't working anyway.
I guess I should store my task in some presitant data storage and everytime user check page newset state should be draw from that storage and send to him.
I'm not and expert but i think best option here is to store all data in Memory until task is done.
Any relational database will be probably to slow, if I will have to constantly update records with newset state of task.
I know I should just synchronize client side timer with server and do not stream constatly time from server. But Big question is here how to get newset state of task pogress when user come back to page after 3 second or 3 hours ?
Any advices how to make it work as expected.
And btw. I'm using pollingduplex and Silverlight.
It sounds like you are using WCF to do something for which it wasn't designed (but I understand the need). I would suggest you look at Workflow Foundation (WF) as a possible solution to your problem. Here is a good explanation:
http://msdn.microsoft.com/library/dd851337.aspx
Here is also a good intro video:
http://channel9.msdn.com/Blogs/mwink/Introduction-to-Workflow-Services-building-WCF-Services-with-WF
Workflow can consume WCF services and it is designed to work over time. It holds data in state until something changes (regardless of time) without consuming excess resources. Also, it allows for persistance and parallel processes.