Function in System.Threading.TimerCallback is not working - c#

I am trying to send an email every day in specific time,
but seems like the function(void SomeMethodRunsAtSpecificTime(object o)) in System.Threading.TimerCallback is not working.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
double TimeOfExecution = 14;
DateTime now = DateTime.Now;
DateTime today7am = now.Date.AddHours(TimeOfExecution);
DateTime next7am = now <= today7am ? today7am : today7am.AddDays(1);
System.Threading.TimerCallback callback = new
System.Threading.TimerCallback(SomeMethodRunsAtSpecificTime);
var timer1 = new System.Threading.Timer(callback, null, next7am - DateTime.Now, TimeSpan.FromHours(24));
}
void SomeMethodRunsAtSpecificTime(object o)
{
//Sending email from someone to someone(this function has no problem)
}

As soon as Page_Load is done executing the Timer goes out of scope. It's the same as if you declared an int in that method. Once the method is done executing, where does the int go? Nowhere. It's gone.
Even if you made the Timer a property of the page (the _Default class) the same is true. The whole class is created just to generate a response (HTML page) and then it also goes out of scope, taking its member variables with it.
A web application isn't really a good tool for doing something at regular intervals. Its purpose is to respond to requests when they come in. When there are no requests the application isn't doing anything except waiting for requests. If no one sends a request to the site for a while the whole application can even shut down, and IIS just starts it up later when a request comes in.
To do something at regular intervals you may want a Windows Service or even a scheduled task.

Related

How to wait for some operation in another class to start next operation in c#?

I know you don't understand the question title. Let me tell you the whole scenario.
I have a class named as Processor. Processor can get notifiable steps or send notification to other application API depending on some condition. Like as below:
Method: SendOrStartProcess(Operation operation)
Method Implementation:
If(operation.steps.any(o=>o.canProcess)){
_service.notify(operations); //This is fine
}
else{
var totalSteps = await _service.getAdjustableSteps(); //It returns the adjustable steps and will invoke event which is subscribed by another class named as GetAdjustableStepsEnd. It can be invoked immediately or after some time.
//Set totalSteps in operationsc.steps and save in database and that's it.
}
Now, I have another class named as "OperationHandler" which subscirbed the GetAdjustableStepsEnd event.
public OperationHandler(IUnityContainer container)
{
_agent.GetAdjustableStepsEnd += GetAdjustableStepsEnd ;
}
public async void GetAdjustableStepsEnd (object sender, GetAdjustableStepsEndEventArgs e)
{
// Here i will call again the above method _processor.SendOrStartProcess(e.Operations);
}
//Now the problem is if event invokes after some time then it is fine because meanwhile i set the status in database. But if it invoked just after the getAdjustableSteps then i call SendOrStartProcess again and it sends getAdjustableSteps again because record is not set in the database. How to overcome this situation. I can not put lock on it because this is used by many clients.

"Invoke or BeginInvoke cannot be called on a control until" on a Label

I've read several questions/answers related to this problem but couldn't find a solution applicable to problem.
I have a form (MainForm) and a button (Upload) on it. When I click on the button (after selecting a file from a ComboBox to be uploaded to the server), it opens another form (UploadBackupForm) and uploads a file to the server. The upload process is controlled in UploadBackupForm and the form looks like this:
This works as long as upload is done once, I mean, the UploadBackupForm is called one time. The second time I click on the Upload button, UploadBackupForm opens and (after uploading some data) it throws an error saying:
System.InvalidOperationException: 'Invoke or BeginInvoke cannot be called on a control until the window handle has been created.'
at this specific line(s):
DurationLabel.Invoke((MethodInvoker)delegate
{
DurationLabel.Text = Count2Duration(count);
});
This has puzzled me because it works when it's done once, and doesn't work at the second time. I have basic knowdledge in C#, so I don't know what's causing this and how to solve it.
MainForm:
private void Upload2ServerButton_OnClick(object sender, EventArgs e)
{
Form UBF = new UploadBackupForm();
UBF.ShowDialog();
}
UploadBackupForm:
public partial class UploadBackupForm : Form
{
public UploadBackupForm()
{
InitializeComponent();
}
public static System.Timers.Timer timer = new System.Timers.Timer();
public static int count = 0;
private void UploadBackup_Load(object sender, EventArgs e)
{
timer.Interval = 1000;
timer.Elapsed += new ElapsedEventHandler(delegate {
count++;
// didn't do any good (this.IsHandleCreated or DurationLabel.IsHandleCreated)
// if (!this.IsHandleCreated)
// {
// this.CreateControl();
// }
DurationLabel.Invoke((MethodInvoker)delegate
{
DurationLabel.Text = Count2Duration(count);
});
});
// upload the archive to the server
new Thread((ThreadStart)delegate
{
FTP.Item[] items = FTP.ListDirectoryDetails(DataIO.FTP.Server, DataIO.FTP.Username, DataIO.FTP.Password, DataIO.FTP.UploadDir);
// here, I upload the file to the server and update the progress bar and the uploaded / total labels
Because the timer variable is static it remains even after the form is closed. It contains a reference to a delegate which holds a reference to the form so the previous instances are kept alive through the lifetime of your application. Also, the single timer posts callbacks to all previous instances along with the current one.
As correctly noted in the comments by Evk, make the timer and count non-static so they are dedicated to each instance of the form.

Mqtt and asp.net web application, need assistance

Im having trouble converting wpf to asp.net using mqtt. My code did not show any error but when i launch and input some text and a button click,it will show me an error
"An exception of type 'System.NullReferenceException' occurred in WebApplication4.dll but was not handled in user code"
public partial class Testing : System.Web.UI.Page
{
MqttClient client;
string clientId;
protected void Page_Load(object sender, EventArgs e)
{
}
public void MainWindow()
{
string BrokerAddress = "test.mosquitto.org";
client = new MqttClient(BrokerAddress);
// register a callback-function (we have to implement, see below) which is called by the library when a message was received
client.MqttMsgPublishReceived += client_MqttMsgPublishReceived;
// use a unique id as client id, each time we start the application
clientId = Guid.NewGuid().ToString();
client.Connect(clientId);
}
void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
string ReceivedMessage = Encoding.UTF8.GetString(e.Message);
txtReceived.Text = ReceivedMessage;
}
protected void btnPublish_Click(object sender, EventArgs e)
{
if (txtTopicPublish.Text != "")
{
// whole topic
string Topic = "" + txtTopicPublish.Text + "";
// publish a message with QoS 2
client.Publish(Topic, Encoding.UTF8.GetBytes(txtPublish.Text), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, true);
}
else
{
Page.ClientScript.RegisterStartupScript(this.GetType(), "Scripts", "<script>alert('You have to enter a topic to publish!')</script>");
}
}
It seems to be a lack of understanding of the ASP.NET page lifecycle.
In WPF, the lifecycle spans from when you run the program til when it is closed; it is statefull. ASP.NET is not; whatever you do in a Page_Load (and the other lifecycle events) will be disposed with the completion of rendering the page.
You have a few ways of solving your problem.
You can keep the MqttClient instance in the Application object. This keeps the instance alive from when the AppPool starts (instantiate the client in the Application_Start event in Global.asax. It is fired when the AppPool starts) and until it shuts down (Application_End, where you get the opportunity to shut your MqttClient down gracefully if you want/need to). It is shared between all users and can be accessed anywhere with Application[key] where key is any string, "MqttClient" for example.
You can keep it in the Session object the same way you would in the Application object. You can use Sesson_Start and Session_End in the same way. The Session object is unique to each user, in terms of it staying alive until the user stops browsing your website.
You can instantiate MqttClient every time you need it or with every Page_Load.

Is is possible to know for sure if a WebBrowser is navigating or not?

I'm trying to find a way for my program to know when a WebBrowser is navigating and when is not. This is because the program will interact with the loaded document via JavaScript that will be injected in the document. I don't have any other way to know when it starts navigating than handling the Navigating event since is not my program but the user who will navigate by interacting with the document. But then, when DocumentCompleted occurs doesn't necessarily mean that it have finished navigating. I've been googling a lot and found two pseudo-solutions:
Check for WebBrowser's ReadyState property in the DocumentCompleted event. The problem with this is that if not the document but a frame in the document loads, the ReadyState will be Completed even if the main document is not completed.
To prevent this, they advise to see if the Url parameter passed to DocumentCompleted matches the Url of the WebBrowser. This way I would know that DocumentCompleted is not being invoked by some other frame in the document.
The problem with 2 is that, as I said, the only way I have to know when a page is navigating is by handling the Navigating (or Navigated) event. So if, for instance, I'm in Google Maps and click Search, Navigating will be called, but just a frame is navigating; not the whole page (on the specific Google case, I could use the TargetFrameName property of WebBrowserNavigatingEventArgs to check if it's a frame the one that is navigating, but frames doesn't always have names). So after that, DocumentCompleted will be called, but not with the same Url as my WebBrowsers Url property because it was just a frame the one that navigated, so my program would thing that it's still navigating, forever.
Adding up calls to Navigating and subtracting calls to DocumentCompleted wont work either. They are not always the same. I haven't find a solution to this problem for months already; I've been using solutions 1 and 2 and hoping they will work for most cases. My plan was to use a timer in case some web page has errors or something but I don't think Google Maps has any errors. I could still use it but the only uglier solution would be to burn up my PC.
Edit: So far, this is the closest I've got to a solution:
partial class SafeWebBrowser
{
private class SafeNavigationManager : INotifyPropertyChanged
{
private SafeWebBrowser Parent;
private bool _IsSafeNavigating = false;
private int AccumulatedNavigations = 0;
private bool NavigatingCalled = false;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSafeNavigating
{
get { return _IsSafeNavigating; }
private set { SetIsSafeNavigating(value); }
}
public SafeNavigationManager(SafeWebBrowser parent)
{
Parent = parent;
}
private void SetIsSafeNavigating(bool value)
{
if (_IsSafeNavigating != value)
{
_IsSafeNavigating = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsSafeNavigating"));
}
}
private void UpdateIsSafeNavigating()
{
IsSafeNavigating = (AccumulatedNavigations != 0) || (NavigatingCalled == true);
}
private bool IsMainFrameCompleted(WebBrowserDocumentCompletedEventArgs e)
{
return Parent.ReadyState == WebBrowserReadyState.Complete && e.Url == Parent.Url;
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
public void OnNavigating(WebBrowserNavigatingEventArgs e)
{
if (!e.Cancel) NavigatingCalled = true;
UpdateIsSafeNavigating();
}
public void OnNavigated(WebBrowserNavigatedEventArgs e)
{
NavigatingCalled = false;
AccumulatedNavigations++;
UpdateIsSafeNavigating();
}
public void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
{
NavigatingCalled = false;
AccumulatedNavigations--;
if (AccumulatedNavigations < 0) AccumulatedNavigations = 0;
if (IsMainFrameCompleted(e)) AccumulatedNavigations = 0;
UpdateIsSafeNavigating();
}
}
}
SafeWebBrowser inherits WebBrowser. The methods OnNavigating, OnNavigated and OnDocumentCompleted are called on the corresponding WebBrowser overridden methods. The property IsSafeNavigating is the one that would let me know if it's navigating or not.
Waiting till the document has loaded is a difficult problem, but you want to continually check for .ReadyState and .Busy (dont forget that). I will give you some general information you will need, then I will answer your specific question at the end.
BTW, NC = NavigateComplete and DC = DocumentComplete.
Also, if the page you are waiting for has frames, you need to get a ref to them and check their .busy and .readystate as well, and if the frames are nested, the nested frames .readystate and .busy as well, so you need to write a function that recursively retreives those references.
Now, regardless of how many frames it has, first fired NC event is always the top document, and last fired DC event is always that of the top (parent) document as well.
So you should check to see if its the first call and the pDisp Is WebBrowser1.object (literally thats what you type in your if statement) then you know its the NC for top level document, then you wait for this same object to appear in a DC event, so save the pDisp to a global variable, and wait until a DC is run and that DC's pDisp is equal to the Global pDisp you've saved during the first NC event (as in, the pDisp that you saved globally in the first NC event that fired). So once you know that pDisp was returned in a DC, you know the entire document is finished loading.
This will improve your currect method, however, to make it more fool proof, you need to do the frames checking as well, since even if you did all of the above, it's over 90% good but not 100% fool proof, need to do more for this.
In order to do successful NC/DC counting in a meaningful way (it is possible, trust me) you need to save the pDisp of each NC in an array or a collection, if and only if it doesn't already exist in that array/collection. The key to making this work is checking for the duplicate NC pDisp, and not adding it if it exists. Because what happens is, NC fires with a particular URL, then a server-side redirect or URL change occurs and when this happens, the NC is fired again, BUT it happens with the same pDisp object that was used for the old URL. So the same pDisp object is sent to the second NC event now occuring for the second time with a new URL but still all being done with the exact same pDisp object.
Now, because you have a count of all unique NC pDisp objects, you can (one by one) remove them as each DC event occurs, by doing the typical If pDisp Is pDispArray(i) Then (this is in VB) comparison wrapped in a For Loop, and for each one taken off, your array count will get closer to 0. This is the accurate way to do it, however, this alone is not enough, as another NC/DC pair can occur after your count reaches 0. Also, you got to remember to do the exact same For Loop pDisp checking in the NavigateError event as you do in the DC event, because when a navigation error occurs, a NavigateError event is fired INSTEAD of the DC event.
I know this was a lot to take, but it took me years of having to deal with this dreaded control to figure these things out, I've got other code & methods if you need, but some of the stuff I mentioned here in relation to WB Navigation being truely ready, haven't been published online before, so I really hope you find them useful and let me know how you go. Also, if you want/need clarification on some of this let me know, unfortunately, the above isn't everything if you want to be 100% sure that the webpage is done loading, cheers.
PS: Also, forgot to mention, relying on URL's to do any sort of counting is inaccurate and a very bad idea because several frames can have the same URL - as an example, the www.microsoft.com website does this, there are like 3 frames or so calling MS's main site that you see in the address bar. Don't use URL's for any counting method.
First I've converted the document to XML and then used my magic method:
nodeXML = HtmlToXml.ConvertToXmlDocument((IHTMLDocument2)htmlDoc.DomDocument);
if (ExitWait(false))
return false;
conversion:
public static XmlNode ConvertToXmlDocument(IHTMLDocument2 doc2)
{
XmlDocument xmlDoc = new XmlDocument();
IHTMLDOMNode htmlNodeHTML = null;
XmlNode xmlNodeHTML = null;
try
{
htmlNodeHTML = (IHTMLDOMNode)((IHTMLDocument3)doc2).documentElement;
xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", ""/*((IHTMLDocument2)htmlDoc.DomDocument).charset*/, ""));
xmlNodeHTML = xmlDoc.CreateElement("html"); // create root node
xmlDoc.AppendChild(xmlNodeHTML);
CopyNodes(xmlDoc, xmlNodeHTML, htmlNodeHTML);
}
catch (Exception err)
{
Utils.WriteLog(err, "Html2Xml.ConvertToXmlDocument");
}
magic method:
private bool ExitWait(bool bDelay)
{
if (m_bStopped)
return true;
if (bDelay)
{
DateTime now = DateTime.Now;
DateTime later = DateTime.Now;
TimeSpan difT = (later - now);
while (difT.TotalMilliseconds < MainDef.IE_PARSER_DELAY)
{
Application.DoEvents();
System.Threading.Thread.Sleep(10);
later = DateTime.Now;
difT = later - now;
if (m_bStopped)
return true;
}
}
return m_bStopped;
}
where m_bStopped is false by default, IE_PARSER_DELAY is a timeout value.
I hope this helps.

Providing multiple instances of a form yet processing events one at a time

I need to be able to let multiple instances of the same form be open as my application can be used in different places at once. On the other hand I need to be able to process the operations during the "OK" event one at a time to ensure data is stored safely and not overwritten by another form instance by accident.
I show my form using the .Show() method as I am using a few delegates in it:
private void newToolStripMenuItem_Click(object sender, EventArgs e)
{
bookingForm = new BookingForm(AddMemberBooking, AddUserBooking, CloseBooking);
bookingForm.Show();
}
I have tried to use the mutex to allow only one event of the OK button being pressed happen at a time, i have combined this with a Thread to meet the criteria i need.
When i click on the "OK" button I am given the following error:
Cross-thread operation not valid: Control 'comboBoxDay' accessed from a thread other than the thread it was created on.
This is the code for my booking form class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace Collection
{
//Allows the class to be serialized
[Serializable()]
public delegate void AddMemberBookingMethod(int date, int time, int mNo);
public delegate void AddUserBookingMethod(int date, int time, string fName, string lName, string pCode);
public delegate void CloseBookingFormMethod();
public partial class BookingForm : Form
{
public CloseBookingFormMethod CloseBookingForm;
public AddMemberBookingMethod AddMemberBooking;
public AddUserBookingMethod AddUserBooking;
private Mutex bookingMut = new Mutex();
private Thread thread;
public bool IsUser;
public BookingForm(AddMemberBookingMethod ambm, AddUserBookingMethod aubm, CloseBookingFormMethod cbfm)
{
InitializeComponent();
AddMemberBooking = ambm;
AddUserBooking = aubm;
CloseBookingForm = cbfm;
checkBoxMember.Checked = true;
//Control.CheckForIllegalCrossThreadCalls = false;
}
private void checkBoxUser_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxUser.Checked)
{
IsUser = true;
checkBoxMember.CheckState = CheckState.Unchecked;
textBoxMNo.Enabled = false;
textBoxFName.Enabled = true;
textBoxLName.Enabled = true;
textBoxPCode.Enabled = true;
}
else
{
IsUser = false;
checkBoxMember.CheckState = CheckState.Checked;
textBoxMNo.Enabled = true;
textBoxFName.Enabled = false;
textBoxLName.Enabled = false;
textBoxPCode.Enabled = false;
}
}
private void checkBoxMember_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxMember.Checked)
{
IsUser = false;
checkBoxUser.CheckState = CheckState.Unchecked;
textBoxFName.Enabled = false;
textBoxLName.Enabled = false;
textBoxPCode.Enabled = false;
}
else
{
IsUser = true;
checkBoxUser.CheckState = CheckState.Checked;
textBoxMNo.Enabled = false;
textBoxFName.Enabled = true;
textBoxLName.Enabled = true;
textBoxPCode.Enabled = true;
}
}
private void buttonOK_Click(object sender, EventArgs e)
{
this.thread = new Thread(new ThreadStart(MakeBooking));
this.thread.Name = "bookingThread";
this.thread.Start();
}
private void MakeBooking()
{
this.bookingMut.WaitOne();
int date = this.comboBoxDay.SelectedIndex;
int time = this.comboBoxTime.SelectedIndex;
if (IsUser)
{
string fName = textBoxFName.Text;
string lName = textBoxLName.Text;
string pCode = textBoxPCode.Text;
AddUserBooking(date, time, fName, lName, pCode);
}
else
{
int mNo = int.Parse(textBoxMNo.Text);
AddMemberBooking(date, time, mNo);
}
this.bookingMut.ReleaseMutex();
CloseBookingForm();
}
private void buttonClose_Click(object sender, EventArgs e)
{
CloseBookingForm();
}
}
}
I realise I may not be doing this in the most efficient way but time is a bit of a factor.
I've researched the error and have heard of using delegates and .Invoke() but I'm still not entirely sure how to fix it.
EDIT:
I've found this code snippet when searching for a fix to my problem. I don't understand where/how I would use it.
if(this.InvokeRequired)
{
this.Invoke(new MyEventHandler(this.CreateAForm()));
return;
}
EDIT2:
Seems the guy finally saw sense, by creating the from with the new word it apparently passes the criteria. I wish I'd have known this before trying to reinvent the wheel.
You are getting this exception because your thread is accessing controls. That's not legal, control properties must only ever be accessed from the UI thread. You're okay on the TextBox.Text property, that one happens to be cached. But not ComboBox.SelectedIndex. And closing the form from another thread is going to bomb too.
Your mutex has nothing to do with it, but keep it if you want to prevent threads from overlapping. Using a delegate's Invoke method isn't going to solve it, that just starts a thread as well. You'll need to collect the info that the thread is going to need in a little helper class and pass that as the argument to the Thread.Start() method.
Closing the form is a bit tricky too, the user might well have already closed it while the thread was running. That's going to cause an ObjectDisposed exception. A quick fix is to set the form's Enabled property to false so the user can't close it. You'll need to use the form's Invoke() method to ensure the closing is done on the UI thread.
Last but not least, if these threads don't take a lot of time (a second or so), consider not using threads at all and display a wait cursor instead.
One simple way to do this is to use the overload of the Thread.Start method that accepts an object: Thread.Start Method (Object). In this object you will store all the data/state necessary in order to make the update.
All the code that references the form and its controls needs to be moved into the OK click event method or refactored out to a method that just returns a data object. Then pass this object into the thread start method.
Some pseudo code:
on_click_event()
{
object data=getFormData();
thread.start(data);
}
There are better ways to do this but this is a quick fix for your code.
I think you could simply disable the OK buttons on other open forms to give users a visual cue. Then you shouldn't even have the issue. Provide a callback delegate to something in the application controller which knows which forms are open. Each form can provide a public method to disable the OK button. Disable to OK button on all the other forms.
I'm not really following your code too well. I would think the mutex could be outside of the form code in the first place (i.e. in the delegates that do the actual work), and if it is within a single application, you could just use the lock (object) method to ensure only one thread is executing a given bit of code.
I'd also like to add that a mutex is not going to stop multiple users on different machiens being able to click OK at the same time. I'm not sure if that's what you meant in your question by a form being run in different places.
I think that AddUserBooking and the other delegate should be responsible for ensuring that they are threadsafe and this should not be part of the UI. If they aren't threadsafe, why aren't they? It's relatively easy to make database commit functions each have their own connection to the database during their operations and thread-safety should not be an issue.

Categories

Resources