Reputation: 65
In one of my C# 4.0 windows app I am facing unusual behaviour with (most probably) BeginInvoke. I have a login form on which an ID is generated each time user tries to login, the ID authorization is written to a file, which login form is watching using FileSystemWatcher. On authorization an event is generated which is captured by main form.
Now my issue is that old IDs show up at the time of verification, even though each time a new instance of LoginForm in created at the time of login process. First time the ID value in strID variable is correct but from then on for each consecutive login, previous IDs show up.
It is difficult for me to explain it in words correctly, so I am attaching the code for login form. The weird behaviour I am facing is at if(arrData[0] == this.strID)
part of GetAuthorizationStatus
function. Please help me understand whether this is an expected behaviour or am I doing something wrong here.
Login form have 3 buttons : btnLogin for getting the ID, btnExit to exit the form and btnAuth for authenticating (I have put it here for testing, instead of another app/service)
Following is the code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.IO;
using System.Windows.Forms;
namespace SampleInvokeTest
{
public class AuthEventArgs : EventArgs
{
private string strID;
private DateTime dtLoginAt;
private bool isAuthorized;
public AuthEventArgs(string ID, DateTime LoginAt, bool isAuthorized)
{
this.strID = ID;
this.dtLoginAt = LoginAt;
this.isAuthorized = isAuthorized;
}
public string ID
{ get{ return this.strID; } }
public DateTime LoginAt
{ get{ return this.dtLoginAt; } }
public bool IsAuthorized
{ get{ return this.isAuthorized; } }
}
public partial class LoginForm : Form
{
delegate void ShowDelegate();
private string strID = "";
private const string FILE_PATH = @"E:\test.txt";
private DateTime dtLastResultReadTime = DateTime.MinValue;
private DateTime dtLoginAt = DateTime.MinValue;
private bool isAuthorized = false;
private string strLoginErrorMessage ="";
public event EventHandler<AuthEventArgs> AuthOccurred;
public LoginForm()
{
InitializeComponent();
FileSystemWatcher fswAuth = new FileSystemWatcher(Path.GetDirectoryName(FILE_PATH));
fswAuth.Filter = Path.GetFileName(FILE_PATH);
fswAuth.NotifyFilter = NotifyFilters.LastWrite;
fswAuth.Changed+= new FileSystemEventHandler(fswAuth_Changed);
fswAuth.EnableRaisingEvents = true;
tmrTimeout.Interval = 5000;
tmrTimeout.Tick+= new EventHandler(tmrTimeout_Tick);
}
void fswAuth_Changed(object sender, FileSystemEventArgs e)
{
DateTime dtTempReadAt = File.GetLastWriteTime(FILE_PATH);
if (dtLastResultReadTime < dtTempReadAt)
{
dtLastResultReadTime = dtTempReadAt;
GetAuthorizationStatus();
}
}
private void GetAuthorizationStatus()
{
if (InvokeRequired)
{
BeginInvoke(new ShowDelegate(GetAuthorizationStatus));
}
else
{
string strData = ",";
tmrTimeout.Enabled = false;
using (FileStream stream = new FileStream(FILE_PATH, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader streamReader = new StreamReader(stream))
{
try
{ strData = streamReader.ReadToEnd(); }
catch (Exception)
{ //log error }
}
}
string[] arrData = strData.Split(new char[] { ',' });
if (arrData.Length == 2)
{
if(arrData[0] == this.strID)
{
if (arrData[1] == "Y")
{
tmrTimeout.Enabled = false;
this.lblWait.Visible = false;
this.btnExit.Enabled = true;
this.btnLogin.Enabled = false;
this.isAuthorized = true;
this.strLoginErrorMessage = "";
onAuthOccurred(new AuthEventArgs(this.strID, this.dtLoginAt, this.isAuthorized));
this.Close();
}
else
{
//Authorization failed
tmrTimeout.Enabled = false;
this.lblWait.Visible = false;
this.btnExit.Enabled = true;
this.btnLogin.Enabled = true;
//also clear local variables
this.strID = "";
this.dtLoginAt = DateTime.MinValue;
this.isAuthorized = false;
this.strLoginErrorMessage = "Authorization denied.";
}
}
}
if (!this.isAuthorized && this.strLoginErrorMessage != "")
{
MessageBox.Show(this.strLoginErrorMessage, "Authorization denied", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
void tmrTimeout_Tick(object sender, EventArgs e)
{
tmrTimeout.Enabled = false;
this.lblWait.Visible = false;
this.btnExit.Enabled = true;
this.btnLogin.Enabled = true;
//also clear local variables
this.strID="";
this.isAuthorized = false;
this.dtLoginAt = DateTime.MinValue;
MessageBox.Show("Timeout occurred while waiting for authorization.", "Authorization timeout", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void onAuthOccurred(AuthEventArgs e)
{
EventHandler<AuthEventArgs> handler = AuthOccurred;
if (handler != null)
{ handler(this, e); }
}
private void btnLogin_Click(object sender, EventArgs e)
{
this.dtLoginAt = DateTime.Now;
this.strID = IDGenerator.GetNewID().ToString();
this.btnLogin.Enabled = false;
this.btnExit.Enabled = false;
tmrTimeout.Enabled = true;
lblWait.Visible = true;
}
private void btnExit_Click(object sender, EventArgs e)
{
if (MessageBox.Show("Do you wish to cancel login?", "Cancel Login", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) == DialogResult.OK)
{
//Clear local variables
this.strID="";
this.isAuthorized = false;
this.dtLoginAt = DateTime.MinValue;
//raise event and exit
onAuthOccurred(new AuthEventArgs(this.strID, this.dtLoginAt, this.isAuthorized));
this.Close();
}
}
void BtnAuthClick(object sender, EventArgs e)
{
File.WriteAllText(FILE_PATH,string.Format("{0},Y",this.strID));
}
}
}
Upvotes: 2
Views: 149
Reputation: 942247
FileSystemWatcher fswAuth = new FileSystemWatcher(...);
This is where the problem started. You must dispose the watcher when your form closes, the FormClosed event is good for that. Or just dropping one from the toolbox, now it is completely automatic. But you don't, it just keeps watching after the window is closed. And keeps raising events.
That usually triggers an ObjectDisposedException, but it doesn't have to. You'll also leak the form object, it cannot be garbage collected. Misery multiplies when you create another instance of this form, now you have two FSWs monitoring for file changes. The old one still uses the old ID, that's what you see back in the file.
Keep in mind that this is not where the problems end, the user could start your program again, now you inevitably have two FSWs that disagree about the proper ID.
You need to re-think this. A server-style app is the usual solution. A short-term workaround is keeping the FSW separate in a static class so there is only ever one instance of it. And ensuring that your program can run only once, a "single-instance app".
Upvotes: 2