So I am trying to create a basic WinForm Application that uses SNMP, from snmpsharpnet.
I have two buttons 'Eye' and 'Jitter' that when one is pressed a timer starts which executes SNMP code inside the timer handler every minute.
I am trying to write the SNMP output to a textbox from the timer handler but everything I try either leads to thread exceptions or a continuous running process when I exit the program.
I have tried so many different things to fix those two errors that I may be screwing everything up but here is the code I have:
using System;
using System.Net;
using SnmpSharpNet;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public static bool stop = false;
static bool min = false, sec = false, eye = false, jitter = false;
static string ipAdd = "";
static System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
static int alarmCounter = 1;
static bool exitFlag = false;
static TextBox textbox;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
textbox = outputBox;
}
private void IPtext_TextChanged(object sender, EventArgs e)
{
ipAdd = IPtext.Text;
}
private void stopButton_Click(object sender, EventArgs e)
{
stop = true;
timer.Stop();
}
// This is the method to run when the timer is raised.
private static void TimerEventProcessor(Object myObject,
EventArgs myEventArgs)
{
timer.Stop();
// If stop button has not been pressed then continue timer.
if (stop == false)
{
textbox.Clear();
// Restarts the timer and increments the counter.
alarmCounter += 1;
timer.Enabled = true;
/*
textbox.Invoke(
new MethodInvoker(
delegate { textbox.AppendText("fsjdaò"); }));
*/
System.IO.StreamWriter file;
if (eye == true)
{
file = new System.IO.StreamWriter("c:/Users/bshellnut/Desktop/Eye.txt", true);
}
else
{
file = new System.IO.StreamWriter("c:/Users/bshellnut/Desktop/Jitter.txt", true);
}
// SNMP community name
OctetString community = new OctetString("public");
// Define agent parameters class
AgentParameters param = new AgentParameters(community);
// Set SNMP version to 2 (GET-BULK only works with SNMP ver 2 and 3)
param.Version = SnmpVersion.Ver2;
// Construct the agent address object
// IpAddress class is easy to use here because
// it will try to resolve constructor parameter if it doesn't
// parse to an IP address
IpAddress agent = new IpAddress(ipAdd);
// Construct target
UdpTarget target = new UdpTarget((IPAddress)agent, 161, 2000, 1);
// Define Oid that is the root of the MIB
// tree you wish to retrieve
Oid rootOid;
if (eye == true)
{
rootOid = new Oid("1.3.6.1.4.1.128.5.2.10.14"); // ifDescr
}
else
{
rootOid = new Oid("1.3.6.1.4.1.128.5.2.10.15");
}
// This Oid represents last Oid returned by
// the SNMP agent
Oid lastOid = (Oid)rootOid.Clone();
// Pdu class used for all requests
Pdu pdu = new Pdu(PduType.GetBulk);
// In this example, set NonRepeaters value to 0
pdu.NonRepeaters = 0;
// MaxRepetitions tells the agent how many Oid/Value pairs to return
// in the response.
pdu.MaxRepetitions = 5;
// Loop through results
while (lastOid != null)
{
// When Pdu class is first constructed, RequestId is set to 0
// and during encoding id will be set to the random value
// for subsequent requests, id will be set to a value that
// needs to be incremented to have unique request ids for each
// packet
if (pdu.RequestId != 0)
{
pdu.RequestId += 1;
}
// Clear Oids from the Pdu class.
pdu.VbList.Clear();
// Initialize request PDU with the last retrieved Oid
pdu.VbList.Add(lastOid);
// Make SNMP request
SnmpV2Packet result;
try
{
result = (SnmpV2Packet)target.Request(pdu, param);
//textbox.Text = "";
}
catch (SnmpSharpNet.SnmpException)
{
textbox.Invoke(
new MethodInvoker(
delegate { textbox.AppendText("Could not connect to the IP Provided."); }));
timer.Stop();
//outputBox.Text += "Could not connect to the IP Provided.";
break;
}
// You should catch exceptions in the Request if using in real application.
// If result is null then agent didn't reply or we couldn't parse the reply.
if (result != null)
{
// ErrorStatus other then 0 is an error returned by
// the Agent - see SnmpConstants for error definitions
if (result.Pdu.ErrorStatus != 0)
{
// agent reported an error with the request
/*Console.WriteLine("Error in SNMP reply. Error {0} index {1}",
result.Pdu.ErrorStatus,
result.Pdu.ErrorIndex);*/
textbox.Invoke(
new MethodInvoker(
delegate { textbox.AppendText("Error in SNMP reply. " + "Error " + result.Pdu.ErrorStatus + " index " + result.Pdu.ErrorIndex); }));
//outputBox.Text = "Error in SNMP reply. " + "Error " + result.Pdu.ErrorStatus + " index " + result.Pdu.ErrorIndex;
lastOid = null;
break;
}
else
{
// Walk through returned variable bindings
foreach (Vb v in result.Pdu.VbList)
{
// Check that retrieved Oid is "child" of the root OID
if (rootOid.IsRootOf(v.Oid))
{
/*Console.WriteLine("{0} ({1}): {2}",
v.Oid.ToString(),
SnmpConstants.GetTypeName(v.Value.Type),
v.Value.ToString());*/
textbox.Invoke(
new MethodInvoker(
delegate { textbox.AppendText(v.Oid.ToString() + " " + SnmpConstants.GetTypeName(v.Value.Type) +
" " + v.Value.ToString() + Environment.NewLine); }));
//outputBox.Text += v.Oid.ToString() + " " + SnmpConstants.GetTypeName(v.Value.Type) +
//" " + v.Value.ToString() + Environment.NewLine;
file.WriteLine(v.Oid.ToString() + " " + SnmpConstants.GetTypeName(v.Value.Type) +
" " + v.Value.ToString(), true);
if (v.Value.Type == SnmpConstants.SMI_ENDOFMIBVIEW)
lastOid = null;
else
lastOid = v.Oid;
}
else
{
// we have reached the end of the requested
// MIB tree. Set lastOid to null and exit loop
lastOid = null;
}
}
}
}
else
{
//Console.WriteLine("No response received from SNMP agent.");
textbox.Invoke(
new MethodInvoker(
delegate { textbox.AppendText("No response received from SNMP agent."); }));
//outputBox.Text = "No response received from SNMP agent.";
}
}
target.Close();
file.Close();
}
else
{
// Stops the timer.
exitFlag = true;
}
}
private void eyeButton_Click(object sender, EventArgs e)
{
outputBox.Text = "Connecting...";
eye = true;
jitter = false;
stop = false;
timer.Tick += new EventHandler(TimerEventProcessor);
// Sets the timer interval to 5 seconds.
timer.Interval = 5000;
timer.Start();
// Runs the timer, and raises the event.
while (exitFlag == false)
{
// Processes all the events in the queue.
Application.DoEvents();
}
}
private void jitterButton_Click(object sender, EventArgs e)
{
outputBox.Text = "Connecting...";
eye = false;
jitter = true;
stop = false;
timer.Tick += new EventHandler(TimerEventProcessor);
// Sets the timer interval to 5 seconds.
timer.Interval = 5000;
timer.Start();
// Runs the timer, and raises the event.
while (exitFlag == false)
{
// Processes all the events in the queue.
Application.DoEvents();
}
}
private void Seconds_CheckedChanged(object sender, EventArgs e)
{
min = false;
sec = true;
}
private void Minutes_CheckedChanged(object sender, EventArgs e)
{
min = true;
sec = false;
}
}
}
I believe you are running into a deadlock on the UI thread.
TimerEventProcessor() is called by your instance of System.Windows.Forms.Timer, which runs on the UI thread. When the timer goes off, I believe it puts a message into the UI thread's message queue to call your TimerEventProcessor() method. That method in turn calls textbox.Invoke(), which puts another message into the queue and then waits for it to be processed.
Your UI thread is now stuck, as it is in the middle of processing a message, but must wait until another message is processed before it can continue. The calls to Application.DoEvents() do you no good, as they are not being called once your program enters TimerEventProcessor(). (They're also unnecessary, since your button click handlers wouldn't be blocking the UI thread anyway.)
Since the timer runs on the UI thread, you can get rid of the textbox.Invoke() calls and just access textbox directly.
Summary:
Replace your calls to textbox.Invoke() with direct access to textbox
Remove your calls to Application.DoEvents()
Note: if you got the Application.DoEvents() logic from the MSDN example for using a timer, they put it there so that the application doesn't quit once the Main function completes.
Update: You can see if this is truly the issue by replacing your calls to textbox.Invoke with the following code. If this code works, then you definitely have a UI message queue deadlocking issue. Also, if this does resolve the issue, I would not recommend keeping this as the solution, but rather, addressing the deadlocking as suggested above.
// Make the request asynchronously
System.IAsyncResult asyncResult = textbox.BeginInvoke(
new MethodInvoker(
delegate { /* insert delegate code here */ }));
// Process the message queue until this request has been completed
while(!asyncResult.IsCompleted) Application.DoEvents();
// Clean up our async request
textbox.EndInvoke(asyncResult);
Since you seem to have your code working, you can ignore the above test code.
Your problem is not related to the timer and all the Invoke statements you use are not necessary. The System.Windows.Forms.Timer class operates in the UI thread. Look here: http://msdn.microsoft.com/en-us/library/system.windows.forms.timer.aspx
Related
I am writing a program which sends and recieves messages to another application.
Some messages have to be sent at certain intervals. For example, I have four messages which get sent, one should be sent every 8 seconds, two are to be sent every 0.5 seconds and one should be sent every 1 second.
The messages seem to be sending fine, however they are all sending at the same time. I used a timestamp to determine that they all seem to be sent every 11 seconds. Below is the code I have implimented:
Here are where the timers are declared:
System.Timers.Timer statusTimer = new System.Timers.Timer(1000);
System.Timers.Timer heightTimer = new System.Timers.Timer(500);
System.Timers.Timer keepAliveTimer = new System.Timers.Timer(8000);
System.Timers.Timer longHeightTimer = new System.Timers.Timer(500);
And here in the main window they are started (this is a WPF project):
statusTimer.Start();
heightTimer.Start();
keepAliveTimer.Start();
longHeightTimer.Start();
statusTimer.Elapsed += delegate { sendStatusMessage(); };
heightTimer.Elapsed += delegate { sendHeightMessage(); };
keepAliveTimer.Elapsed += delegate { sendKeepAliveMessage(); };
longHeightTimer.Elapsed += delegate { sendLongWarpMessage(); };
Here is an example of a method which is should be fired when the time is elapsed (there is not much point in showing them all as they are all basically the same):
public void sendStatusMessage()
{
sendMessage(status_msg.TranslateToMessage());
Dispatcher.Invoke(DispatcherPriority.Normal, (System.Action)delegate //change the priority so you can use UI
{
statusMsgLbl.Content = "Status Msg Sent: " + DateTime.Now.ToLongTimeString();
});
}
And finally this the method which sends the message, as bytes, to the other application using a network stream:
public void sendMessage(byte[] toSend)
{
try
{
lock (NSLock)
{
for (int i = 0; i < toSend.Length; i++)
{
nwStream.WriteByte(toSend[i]);
nwStream.Flush();
}
}
}
catch { MessageBox.Show("Unable to send message"); }
}
You may try to use a DispatcherTimer with an async Tick event handler:
DispatcherTimer statusTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1.0)
};
...
statusTimer.Tick += async (s, e) =>
{
var message = status_msg.TranslateToMessage());
await nwStream.WriteAsync(message, 0, message.Length);
statusMsgLbl.Content = "Status Msg Sent: " + DateTime.Now.ToLongTimeString();
};
statusTimer.Start();
The date is the same because its the date of code running in Invoke, you have to capture date before invoking:
public void sendStatusMessage()
{
sendMessage(status_msg.TranslateToMessage());
var text = "Status Msg Sent: " + DateTime.Now.ToLongTimeString();
Dispatcher.Invoke(() => statusMsgLbl.Content = text);
lets say I have a GroupBox with several Labels. In these Labels, various IP-related information are displayed. One info is the external IP address of the machine.
string externalIP = "";
try
{
WebRequest request = WebRequest.Create("http://checkip.dyndns.org/");
request.Timeout = 3000;
System.Threading.Tasks.Task<System.Net.WebResponse> response = request.GetResponseAsync();
using (StreamReader stream = new StreamReader(response.Result.GetResponseStream()))
{
if (response.Result.ContentLength != -1)
{
externalIP = stream.ReadToEnd();
}
}
}
catch (Exception e)
{
externalIP = "Error.";
}
if (externalIP == "")
{
return "No service.";
}
else
{
return externalIP = (new Regex(#"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")).Matches(externalIP)[0].ToString();
}
This method is called from following code:
private void updateNetworkIP()
{
string ip4e = "External IPv4: " + getExternalIPv4();
lblIP4external.Text = ip4e;
//Get some more info here.
}
How do I execute the code after getExternalIPv4() even when it's not finished yet? It works when setting a TimeOut like I did above but sometimes the request just takes a little longer but still completes successfully. So I want to still be able to display the external IP but continue to execute the other methods for refreshing the GroupBox.
The BackgroundWorker will deliver what you are after. Sample code:
BackgroundWorker bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(getExternalIPv4Back);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(writeLabel);
bg.RunWorkerAsync();
//The code below this point will be executed while the BackgroundWorker does its work
You have to define getExternalIPv4Back as a DoWork Event Method and include inside it the code to be executed in parallel; also writeLabel as a RunWorkerCompleted Event(required to edit the label without provoking muti-threading-related errors). That is:
private void getExternalIPv4Back(object sender, DoWorkEventArgs e)
{
IP = "External IPv4: " + getExternalIPv4(); //IP -> Globally defined variable
}
private void writeLabel(object sender, RunWorkerCompletedEventArgs e)
{
lblIP4external.Text = IP;
}
i'm working with ping Librarry in net 3.5 to check the presence of IP.
take a look at the code below:
public void PingIP(string IP)
{
var ping = new Ping();
ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted); //here the event handler of ping
ping.SendAsync(IP,"a");
}
void ping_PingCompleted(object sender, PingCompletedEventArgs e)
{
if (e.Reply.Status == IPStatus.Success)
{
//On Ping Success
}
}
Then i execute the code through Thread or backgroundworker.
private void CheckSomeIP()
{
for (int a = 1; a <= 255; a++)
{
PingIP("192.168.1." + a);
}
}
System.Threading.Thread checkip = new System.Threading.Thread(CheckSomeIP);
checkip.Start();
Well, here is the problem:
If i start the thread then i would to close the application (Close with Controlbox at corner), i would get "App Crash"
although i have closed/abort the thread.
I think the problem is event handler? as they still working when i'm closing the Application so that i will get "App Crash"
What would be the best way to solve this case?
I think, on a successfull Ping, you are trying to update the interface from within the Thread, which will cause an CrossThreadingOperation exception.
Search the Web for ThreadSave / delegates:
public void PingIP(string IP)
{
var ping = new Ping();
ping.PingCompleted += new PingCompletedEventHandler(ping_PingCompleted); //here the event handler of ping
ping.SendAsync(IP,"a");
}
delegate void updateTextBoxFromThread(String Text);
void updateTextBox(String Text){
if (this.textbox1.InvokeRequired){
//textbox created by other thread.
updateTextBoxFromThread d = new updateTextBoxFromThread(updateTextBox);
this.invoke(d, new object[] {Text});
}else{
//running on same thread. - invoking the delegate will lead to this part.
this.textbox1.text = Text;
}
}
void ping_PingCompleted(object sender, PingCompletedEventArgs e)
{
if (e.Reply.Status == IPStatus.Success)
{
updateTextBox(Text);
}
}
Also on "quitting" the application, you may want to cancel al running threads. Therefore you need to keep the reference on every thread you start somewhere in your application. in the formClosing-Event of your Main-Form, you can force all (running) threads to stop.
I have created 2 programs. Both use timers with interval set to 250 milliseconds.
The problem is that my first program doesn't lock then I run it, and the second locks with out giving me ability to click stop button. By saying locks I mean that program runs do it's job until I stop it from VS.
I don't understand why I can't stop my first program while the basic same way work for other program. Any ideas?
Program that locks:
private void btnScan_Click(object sender, EventArgs e)
{
tmrInterval.Interval = (int)nudInterval.Value;
tmrInterval.Start();
}
private void ScanPort(IPAddress address, int port)
{
using (TcpClient client = new TcpClient())
{
IAsyncResult result = client.BeginConnect(address, port, null, null);
if (result.AsyncWaitHandle.WaitOne((int)nudTimeout.Value, false)) txtDisplay.AppendText("Port: " + port + " is open." + Environment.NewLine);
else txtDisplay.AppendText("Port: " + port + " is closed." + Environment.NewLine);
}
}
private void btnStop_Click(object sender, EventArgs e)
{
tmrInterval.Stop();
}
private void tmrInterval_Tick(object sender, EventArgs e)
{
ScanPort(IPAddress.Parse(txtIP.Text), currentPort);
currentPort++;
if (currentPort == nudTo.Value) tmrInterval.Stop();
}
Program that doesn't lock:
void tmrPingInterval_Tick(object sender, EventArgs e)
{
if (txtTo.Text == string.Empty) Ping(IPAddress.Parse(ip2str(startIP)));
else
{
if (currentIP >= endIP) tmrPingInterval.Stop();
Ping(IPAddress.Parse(ip2str(currentIP)));
currentIP++;
}
}
private void btnPing_Click(object sender, EventArgs e)
{
if (txtFrom.Text != string.Empty)
{
txtFrom.Enabled = false;
txtTo.Enabled = false;
txtDisplay.Text = string.Empty;
tsslPingCount.Text = string.Empty;
count = 0;
open = 0;
closed = 0;
tmrPingInterval.Interval = int.Parse(nudInterval.Value.ToString());
try
{
startIP = str2ip(txtFrom.Text);
if (txtTo.Text != string.Empty) endIP = str2ip(txtTo.Text);
currentIP = startIP;
tmrPingInterval.Start();
}
catch
{
MessageBox.Show("Input must be in IP format: 100.100.100.100", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
txtFrom.Enabled = true;
txtTo.Enabled = true;
}
}
else MessageBox.Show("IP field cannot be empty!", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void btnStop_Click(object sender, EventArgs e)
{
txtFrom.Enabled = true;
txtTo.Enabled = true;
tmrPingInterval.Stop();
}
private void Ping(IPAddress address)
{
Ping pingSender = new Ping();
PingOptions options = new PingOptions();
if (cbDontFragment.Checked) options.DontFragment = true;
else options.DontFragment = false;
string data = string.Empty;
int dataCounter = 0;
options.Ttl = (int)nudTTL.Value;
for (int i = 0; i < nudData.Value; i++)
{
dataCounter++;
if (dataCounter == 10) dataCounter = 0;
data += dataCounter.ToString();
}
byte[] buffer = Encoding.ASCII.GetBytes(data);
int timeout = 120;
try
{
PingReply reply = pingSender.Send(address, timeout, buffer, options);
if (reply.Status == IPStatus.Success)
{
}
else
{
}
}
catch (Exception ex)
{
txtDisplay.SelectedText += Environment.NewLine + ex.Message;
}
}
I'm going to make a guess here since we don't have enough information. I am guessing that tmrInterval in a System.Windows.Forms.Timer. If that is the case, the what's happening is that when the timer ticks, it is handed as a windows message (the same way that mouse clicks, keystrokes, and everything else that make your application appear 'not frozen' are handled).
In the first application, you are doing something that takes substantial time- opening a TCP port, then waiting for a (presumably) large number of milliseconds until the TCP connects or doesnt, then almost immediately doing it again the next time that the timer ticks. You are literally never giving the application a chance to respond to mouse clicks and keystrokes because the UI thread is busy trying to connect to some random ports somewhere.
In the second application, you are doing something relatively quick- just sending a ping somewhere. what you will probably see in the second application is that your application will 'stutter'- become unresponsive for just a second or less while it pings whatever other machine you pointed it at. It doesn't get hung because the UI thread isn't being given that much work to do.
To fix both of these, you should look into implementing a BackgroundWorker. Timers (of any sort) are not generally considered a good way to spawn background tasks in .NET.
About the first sample:
I guess from the context that the timer is a Windows.Forms.Timer. In the elapsed event you are executing a WaitOne(), and so effectively block the Messagepump.
As a possible solution, replace the Timer with a Threading.Timer to decouple the I/O from the main Thread.
C# 2005
I am using a background worker to process some login information. However, the background worker has to stop and wait for 2 events to happen. Once these have finished the background worker can complete its job. They are callbacks that will call the Set() method of the AutoResetEvent.
So I am using AutoResetEvent to set when these 2 events have finished. However, I seemed to be getting this error message:
"Exception has been thrown by the target of an invocation."
And Inner exception
Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index".
The exception usually fires when the registration success leaves scope.
Many thanks for any advice
The code for the background worker.
// Waiting for 'Account in use' and 'Register success or failure'
AutoResetEvent[] loginWaitEvents = new AutoResetEvent[]
{
new AutoResetEvent(false),
new AutoResetEvent(false)
};
private void bgwProcessLogin_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Wait until event is set or timeout");
loginWaitEvents[0].WaitOne(3000, true);
if (this.accountInUseFlag)
{
if (this.lblRegistering.InvokeRequired)
{
///this.lblRegistering.Invoke(new UpdateRegisterLabelDelegate(this.UpdateRegisterLabel), "Account in use");
}
else
{
///this.lblRegistering.Text = "Account in use";
}
// Failed attemp
e.Cancel = true;
// Reset flag
//this.accountInUseFlag = false;
return;
}
else
{
// Report current progress
//this.bgwProcessLogin.ReportProgress(7, "Account accepted");
}
Console.WriteLine("Just Wait the result of successfull login or not");
loginWaitEvents[1].WaitOne();
Console.WriteLine("Results for login registionSuccess: [ " + registerSuccess + " ]");
if (this.registerSuccess)
{
// Report current progress
//this.bgwProcessLogin.ReportProgress(7, "Register Succesfull");
// Reset flag
//this.registerSuccess = false;
}
else
{
if (this.lblRegistering.InvokeRequired)
{
//this.lblRegistering.Invoke(new UpdateRegisterLabelDelegate(this.UpdateRegisterLabel), "Failed to register");
}
else
{
// this.lblRegistering.Text = "Failed to register";
}
// Failed attemp
e.Cancel = true;
return;
}
}
// Wait for the callback to set the AutoResetEvent
// Error sometimes happens when the function leaves scope.
private void VaxSIPUserAgentOCX_OnSuccessToRegister(object sender, EventArgs e)
{
Console.WriteLine("OnSuccessToRegister() [ Registered successfully ]");
this.registerSuccess = true;
this.loginWaitEvents[1].Set();
}
// If the flag is not set, then just time out after 3 seconds for the first LoginWaitEvent.waitOne(3000, true)
private void VaxSIPUserAgentOCX_OnIncomingDiagnostic(object sender, AxVAXSIPUSERAGENTOCXLib._DVaxSIPUserAgentOCXEvents_OnIncomingDiagnosticEvent e)
{
string messageSip = e.msgSIP;
//Indicates that a user is already logged on (Accout in use).
string sipErrorCode = "600 User Found";
if (messageSip.Contains(sipErrorCode))
{
// Set flag for account in use
this.accountInUseFlag = true;
Console.WriteLine("OnIncomingDiagnostic() WaitEvent.Set() accountInUseFlag: " + this.accountInUseFlag);
loginWaitEvents[0].Set();
}
}
There is most likely an indexing error in the UpdateRegisterLabel method.
Get a Stack Trace from the inner exception, it should point you more closely to where it is.