I am trying to create a simple Network Tool to ping all possible IPs on your local subnet and provide a list of such IPs in a DataGridView. I am new to having to consider threading which is a good thing to come across as a budding programmer. Sorry, but you are probably going to have to do some explaining to me, but in my mind this should work. Before I tried putting it in a backgroundworker thread, the application would just hang and give me a "Not Responding".
thanks ahead of time.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
count = 0;
for (int j = 1; j < 255; j++)
for (int i = 1; i < 255; i++)
{
Ping ping = new Ping();
PingReply pingreply = ping.Send(IPAddress.Parse(locip[0] + "." + locip[1] + "." + j + "." + i));
if (pingreply.Status == IPStatus.Success)
{
status = "o";
repAddress = pingreply.Address.ToString(); ;
repRoundtrip = pingreply.RoundtripTime.ToString();
repTTL = pingreply.Options.Ttl.ToString();
repBuffer = pingreply.Buffer.Length.ToString();
string[] lineBuffer = { status, repAddress, repRoundtrip, repTTL, repBuffer };
ipList.Rows.Add(lineBuffer);
count += 1;
progressBar.Value += 1;
}
}
}
You cannot access directly the progressBar1 (or any other UI element) from the backgroundWorker1 "DoWork" event, you have to use the backgroundWorker1.ProgressChanged method and handle ProgressChanged event:
// instead of progressBar.Value += 1
// use the following
const int total = 254 * 254;
backgroundWorker1.ReportProgress(count / total);
WorkerReportsProgress should be assigned to true
and the event of ProgressChanged to the following method
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// assuming the Minimum = 0 and Maximum = 100 on progressBar
progressBar.Value = e.ProgressPercentage;
}
Part of the problem is that you are directly accessing a UI element from your background thread. The field progressBar is presumably a UI progress bar control and can only be safely accessed from the UI thread. You must use a call to .Invoke to set this value from the UI thread.
progressBar.Invoke(new MethodInvoker(UpdateProgressBarbyOne));
...
private void UpdateProgressBarByOne() {
progressBar.Value += 1;
}
Ah I love threading. It makes programs so much more interesting...
So as I started off learning about how to make responsive applications I came across the function: Application.DoEvents()
(http://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents.aspx)
What this does is causes your form to process some of the window events it's receiving. I think that your code could change to include a call after each ping request...
i.e. within the on click event handler
count = 0;
for (int j = 1; j < 255; j++)
for (int i = 1; i < 255; i++)
{
Ping ping = new Ping();
PingReply pingreply = ping.Send(IPAddress.Parse(locip[0] + "." + locip[1] + "." + j + "." + i));
if (pingreply.Status == IPStatus.Success)
{
status = "o";
repAddress = pingreply.Address.ToString(); ;
repRoundtrip = pingreply.RoundtripTime.ToString();
repTTL = pingreply.Options.Ttl.ToString();
repBuffer = pingreply.Buffer.Length.ToString();
string[] lineBuffer = { status, repAddress, repRoundtrip, repTTL, repBuffer };
ipList.Rows.Add(lineBuffer);
count += 1;
progressBar.Value += 1;
}
Application.DoEvents(); //but not too often.
}
Now this was back in the pre dot net days and it's survived till now however, it's not something that you should take lightly. If you click another button on the form it will start off another thread that will attempt to execute and if you're not careful cause thread exceptions on your form. Some developers will tell you don't use this but since your starting off I'd say give it a shot :)
I might not use this method depending on the application. Instead what I would do it actually do is to create several processing "trains"; one for each cpu core that the system had. I'd add the ips to be scanned to a queue object and then I would start up 2 to 4 instances of threads (http://msdn.microsoft.com/en-us/library/system.threading.thread.aspx) each of which taking an item off the queue in turn, process the information (i.e. do the ping logic) and put the result on another queue; and output queue. Every time a train would finish an item for work it would raise an event at the other end of which there would be a handler in the form. Using the Invoke to make thread safe calls (http://msdn.microsoft.com/en-us/library/ms171728.aspx) on my form I would update the UI's information accordingly.
Threading is fun dude :) over time you can find that you can use MSMQ to make a system that uses the multicores of other computers to do jobs such as image processing (or something with pa....... ;)
Related
I'm having a problem.
So I've built an app which displays data in the form of chart and a datagridview. They are both responsive. That means they rescale and move with the data. It takes some computation power I guess.
At the same time I have timers cause it all runs periodically with f=4Hz.
And now: When I run the app and switch on the periodical readout the app hangs during resizing. How could I prevent it?
I've already tried to use a backgroundworker, but the problem occurs in the moment of accessing to the datagridview and chart which are declared (and also used) in the "other thread" (as the VS said)
So.. How could I prevent it?
Maybe I should utilise the backgroundworker in the other way?
My attempts with the backgroundworker:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Control.CheckForIllegalCrossThreadCalls = false;
if (!GetConnectionStatus())
{
stop_ticking();
if (MessageBox.Show("Device not connected", "Connection status", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry)
messaging();
else
return;
}
// TEMP READ
Read_temp(tlist);
float[] t = new float[3];
float[] r = new float[3];
float[] av = new float[1];
float[] st = new float[1];
// TEMP IMPORT
tlist.Give_current_temp(t, r, av, st);
string time_stamp = tlist.Give_current_time();
rows_nr++;
// ADDING TO GRID
dataGridView1.Invoke(new Action(() => { dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() }); }));
//dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() });
dataGridView1.Invoke(new Action(() => { dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1; }));
//dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
// ADDING TO CHART
for (int i = 0; i < 3; i++)
chart1.Invoke(new Action(() => { chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i])); }));
//chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i]));
chart1.Invoke(new Action(() => { chart1.Series["average"].Points.AddXY((rows_nr), (av[0])); }));
//chart1.Series["average"].Points.AddXY((rows_nr), (av[0]));
//chart1.Series["std1"].Points.AddXY((rows_nr), (av[0] + Math.Abs(st[0])));
//chart1.Series["std2"].Points.AddXY((rows_nr), (av[0] - Math.Abs(st[0])));
// MOVING CHART
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
chart1.Series[series_names[i]].Points.RemoveAt(0);
chart1.Series["average"].Points.RemoveAt(0);
//chart1.Series["std1"].Points.RemoveAt(0);
//chart1.Series["std2"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
dataGridView1.Rows.RemoveAt(0);
}
chart1.Invoke(new Action(() => { chart1.ChartAreas[0].RecalculateAxesScale(); }));
//chart1.ChartAreas[0].RecalculateAxesScale();
}
Please take a look at background worker sample. You are doing it wrong. Background worker DoWork should not call UI controls and is executed in non UI thread, it should execute time consuming computing and call worker.ReportProgress(). While ReportProgress method can access UI controls and code in this method is executed in UI thread.
Some chart controls are lugging when adding/removing points. Maybe it hangs because it lugs. Make updates less frequently (1 in 1 second for example) and see whether it hangs or not.
https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netframework-4.7.2
Wrap operations in Stopwatch and use System.Diagnostics.Debug.WriteLine to trace execution flow and time spent on the operations.
Moving chart part does not work because it accesses UI elements in non ui thread without Invoke to UI thread.
If it was not Background worker I would write it this way:
// MOVING CHART
chart1.Invoke(new Action(()=>
{
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
chart1.Series[series_names[i]].Points.RemoveAt(0);
chart1.Series["average"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
}
}
));
I wouldn't wrap each operation in separate Invokes as well.
As for your question it's insufficient information to detect what is wrong please provide minimum viable runnable sample which demonstrates the problem.
As #Access Denied states you should improve separation between GUI and Background worker threads. You could execute // TEMP READ and // TEMP IMPORT operations on background thread and make a call to the GUI thread via .Invoke method when all the data is ready. Read "How to: Make Thread-Safe Calls to Windows Forms Controls" article for more information.
When you add/update data in your DataGridView use .BeginUpdate/.EndUpdate methods to prevent control update until all the data is refreshed.
Other approach is to use Virtual mode. It's especially usefull if you have many items in grid.
When working with a background thread you must not create, update, or even access any UI element.
You need to separate the work that retrieves your data (the slow part) from the work that updates the chart (which is very fast).
It really comes down to doing it like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (!GetConnectionStatus())
{
stop_ticking();
return;
}
// TEMP READ
Read_temp(tlist);
float[] t = new float[3];
float[] r = new float[3];
float[] av = new float[1];
float[] st = new float[1];
// TEMP IMPORT
tlist.Give_current_temp(t, r, av, st);
string time_stamp = tlist.Give_current_time();
rows_nr++;
chart1.Invoke(new Action(() =>
{
// ADDING TO GRID
dataGridView1.Rows.Add(new object[] { rows_nr, time_stamp, av[0], st[0], (t[0]).ToString(), (r[0]).ToString(), (t[1]).ToString(), (r[1]).ToString(), (t[2]).ToString(), (r[2]).ToString() });
dataGridView1.FirstDisplayedScrollingRowIndex = dataGridView1.RowCount - 1;
// ADDING TO CHART
for (int i = 0; i < 3; i++)
{
chart1.Series[series_names[i]].Points.AddXY((rows_nr), (t[i]));
}
chart1.Series["average"].Points.AddXY((rows_nr), (av[0]));
// MOVING CHART
if (chart1.Series[series_names[0]].Points.Count > nr_of_noints_graph)
{
for (int i = 0; i < 3; i++)
{
chart1.Series[series_names[i]].Points.RemoveAt(0);
}
chart1.Series["average"].Points.RemoveAt(0);
chart1.ChartAreas[0].AxisX.Minimum = rows_nr - (nr_of_noints_graph - 1);
chart1.ChartAreas[0].AxisX.Maximum = rows_nr;
dataGridView1.Rows.RemoveAt(0);
}
chart1.ChartAreas[0].RecalculateAxesScale();
}));
}
If you have to show a MessageBox then you also need to invoke that.
There is a console application that simulates router functionality (packet swithcing and so on). I made a simple Windows Form that should show how much each router's socket is used. Each socket has different capacity and each Form "connected" to socket should show how much capacity there is still available.
My code so far:
static void Main(string[] args)
{
//get number of router's sockets
args = Environment.GetCommandLineArgs();
int socketnumber = Convert.ToInt32(args[2]);
//get sockets' capacity
int[] array = new int[socketnumber];
for (int i = 0; i < socketnumber; i++)
{
array[i] = Convert.ToInt32(args[3 + i]);
}
Now, LRM is a WinForm that shows each socket's (or, more accurately, link's attached to socket) status, so I initialize it and give it parameters.
LRM[] lrm = new LRM[socketnumber];
for (int i = 0; i < socketnumber; i++)
{
lrm[i] = new LRM();
lrm[i].Show();
a++;
}
for (int i = 0; i < socketnumber; i++)
{
Console.WriteLine(array[i]);
lrm[i].capacity = array[i];
lrm[i].available = array[i];
lrm[i].WriteCapacity(lrm[i].capacity);
lrm[i].WriteAvailable(lrm[i].available);
}
WriteCapacity and WriteAvailable are methods that are invoked by router to update values on LRM, like that:
public void WriteAvailable(int ava)
{
MethodInvoker mi = delegate ()
{
textBox2.Text = ava.ToString();
};
Invoke(mi);
}
Now, current problems I have:
1) Running multiple windows forms from console AND maintaining their functionality (all of them simultaneously), when number of windows forms varies (is set by command line argument)
2) Updating LRM values. When using show() method it just displays the form, it does not give it any values, and soon after form stops to respond, giving windows error. When using Application.Run() method, one form runs nicely, but neither other LRMs nor router's console works.
I appreciate Alexander Petrov's answer, but I found a way to solve my problem.
I tried Thread approach. First I made most of my variables static:
static int[] array;
static LRM[] lrm;
static int port;
then I made method that will be acting as thread starting method
private static void startLRM(int i)
{
Console.WriteLine(i);
lrm[i].Text = "LRM R" + ((port / 10) - 120).ToString() + " S" + a.ToString();
a++;
Console.WriteLine(array[i]);
lrm[i].capacity = array[i];
lrm[i].available = array[i];
lrm[i].WriteCapacity(lrm[i].capacity);
lrm[i].WriteAvailable(lrm[i].available);
lrm[i].ShowDialog();
}
Then, in Main() method, I allocated memory for link resource managers,and in for loop I made as many LRMs as were needed.
lrm = new LRM[socketnumber];
for (int i = 0; i < socketnumber; i++)
{
lrm[i] = new LRM();
Thread thread = new Thread(() => startLRM(i));
thread.Start();
Thread.Sleep(150);
}
Sleeping was actually necessary, because for unknown reasons I got errors when it was not there.
Finally, I get what I needed - a console application, which shows what is happening, and few windows forms that shows in nice manner some values.
I am making a simple wpf application. It has a button and a textbox and when you click the button it should update the textbox the whole time(see code). I have 3 problems:
It doesn't update the textbox in the loop.
When I click the button It is stuck and I can't close the application(only with taskmanager and stop debugging).
If i leave the textbox stuck and do not close it. After a minute or so I get this error: ContextSwitchDeadlock occurred with the break mode screen.
I have tried to solve the problem by searching on the internet but I didn't succeed. I hope you guys are able to solve it:). And the code is here:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Play_Click(object sender, RoutedEventArgs e)
{
int x = RandomNumber();
}
private string TextboxData;
private int RandomNumber()
{
int x = 0;
int i = 0;
Random rng = new Random();
do
{
x = rng.Next(1, 1000000);
i++;
TextboxData += "\r\nAl zo vaak :O" + i; //the rng loop
textBox.Text = TextboxData;
}
while (x != 1);
TextboxData += "\r\nHij heeft zo vaak geprobeerd 1 te halen " + i;
textBox.Text = TextboxData + Environment.NewLine;
return x;
}
}
The problem is that all your actions are made in main GUI thread.
That means that your program can't redraw interface because it's busy executing code in loop.
In order to achieve desired results you need to execute your loop asynchronous. But we must keep in mind that GUI should be updated only from GUI thread( it's framework requirement).
Due to quick calculations the texbox will be updated too often. You may consider using Thread.Sleep(100).
Here's solution:
private async void Play_Click(object sender, RoutedEventArgs e)
{
int x = await RandomNumber();
}
private async Task<int> RandomNumber()
{
int x = 0;
int i = 0;
string text = string.Empty;
Random rng = new Random();
// This will start work in background. Leaving GUI thread to it's own tasks.
await Task.Factory.StartNew(() =>
{
do
{
x = rng.Next(1, 1000000);
i++;
text = "\r\nAl zo vaak :O" + i.ToString(); //the rng loop
// This will invoke textbox update in GUI thread satisfying update requirement.
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Input,
new Action(() => { textBox.AppendText(text); }));
// We will make it slower in order to see updates at adequate rate.
Thread.Sleep(100);
}
while (x != 1);
});
// thanks to await we will have this code executed after we found our x==1.
text = "\r\nHij heeft zo vaak geprobeerd 1 te halen " + i;
textBox.AppendText(text);
return x;
}
Don't forget to add following usings:
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
EDIT: I don't know why you want to have return value from RandomNumber() (it will always be 1. so it's pontless). But to handle that correctly we need to add async to our signatures in order to use await so we could get a value, execute code after awaited block and return result.
It's a pretty complicated topic, which I will only mention here in order to have correct solution. If you're interested - google '.net async await'. But I suggest to leave it for future :)
Also, I removed TextBoxData field and replaced it with local variable and used textbox.AppendText() as #KevinKal suggested.
I'm working on a program that's supposed to establish "n" many SSH connections with a remote Linux server, and run time consuming commands on each connection. The "time consuming operation" is basically running a script that sets up Wireshark and listens to the traffic.
For this I'm using the SharpSSH library for C# and n many BackgroundWorkers as threads. Also for simplicity, the code below has n=2 BGW threads and SSH connections.
Code:
// runs when start is pressed
private void startButton_Click_1(object sender, EventArgs e)
{
sb = new StringBuilder();
DateTime timeNow = DateTime.Now;
clickTime = timeNow.ToString("yyyyMMddhhmmssfff"); // store the exact time of the click
bw = bwArray[0];
int index = 0; // ignore these 2 constants
foreach (BackgroundWorker bgw in bwArray)
{
if (bgw.IsBusy != true)
{
bgw.RunWorkerAsync();
// runWorkerAsync for every BackgroundWorker in the array
//index++;
}
}
}
// runWorkerAsync leads the BGWorker to this function
private void bw_doWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
if (worker.CancellationPending == true)
{
e.Cancel = true;
}
else
{
// let the UI know of button changes
int p = 0;
object param = "something"; // use this to pass any additional parameter back to the UI
worker.ReportProgress(p, param);
// UI notifying part ends here
// for the simplex case
if (numberOfConnections == 1)
startOperation();
// for the multiplex case
else if (numberOfConnections > 1)
{
//while (p < numberOfConnections)
//{
multiStartOperation();
// p++;
//}
}
Thread.Sleep(500);
}
}
// will be called for all ssh connections (in multiplex case)
private void multiStartOperation()
{
string[] command1Array = { "host2", "host2" };
string[] command2Array = { clickTime + "_h2", clickTime + "_h2" };
for (int index = 0; index < numberOfConnections; index++)
{
// shellArray is an array of SshExec objects
shellArray[index] = new SshExec(IPAddress, username, password);
try
{
shellArray[index].Connect();
}
catch (JSchException se)
{
Console.Write(se.StackTrace);
System.Windows.Forms.MessageBox.Show("Couldn't connect to the specified port.", "Connection Error!");
}
sb.Append(shellArray[index].RunCommand(command1Array[index]) + Environment.NewLine);
// first command is host3, or host4 etc.
// below is the time consuming command to run
string command = "./logcap.sh -c " + command2Array[index] + " -z";
// sb is a global stringBuilder object,
// to which the command output is appended
sb.Append(shellArray[index].RunCommand(command));
}
}
My problem is the following:
When I press the start button on the GUI, both connections should start and run the script. Whereas in the code given above, the first index of shellArray (which consists of SshExec objects) gets connected, prepares the commands and runs the time consuming command, at which point the program goes back to the UI, without even starting the second connection. This is obviously because of the for loop, but I couldn't figure out how to work around this yet.
I need to get the other backgroundworker to establish and run the second command with the second server, so that when I press the stop button on the GUI all connections and threads can stop all together.
PS: The commands will not stop running unless the user clicks stop, which sends a Ctrl-C signal to the server.
I'm relatively new to all the multithreading and networking concepts, so if there is any confusion or mistake please let me know.
Have a nice day.
Thank you for your answers, and the welcome. :)
The problem indeed was not being able to run multiple backgroundworkers at the same time.
I managed to solve the issue. It turns out that all I had to figure out was how to assign backgroundworkers to SSH connections. To do that, I created a class as follows:
class BGW
{
private BackgroundWorker bgw;
private int index;
//getters, setters, constructors...
}
After this, I converted bwArray which was an array of BackgroundWorkers into an array of BGW objects. At initialization, I assigned each BGW object an index.
Instead of having the stupid loop within multiStartOperation(), I sent an integer parameter to multiStartOperation() and that function used that index to reach the allocated backgroundworker.
So far it seems to work.
Have a nice day.
I am brand new to the idea of BackgroundWorkers, so this has me a little perplexed.
So I created a new WPF Application and created a BackgroundWorker and List class variable:
public partial class MainWindow : Window
{
private BackgroundWorker bw = new BackgroundWorker();
private List<int> tempList = new List<int>();
...
Then I use the BackgroundWorker to populate that list: (In the same class btw)
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Random r = new Random();
for (int j = 1; j <= 100; j++)
{
for (int i = 0; i < 1000; i++)
{
tempList.Add(r.Next(100));
}
...
}
}
Now here is the part that's getting me...
The code that populates that list seems to be working fine. When I step through its execution, *it behaves as I have intended until the exact moment the code exits the bw_DoWork method.* After that point, it reverts back to an empty list. I changed the list to static at one point but nothing changed.
So why is this List not persisting throughout the programs execution?
I am(was) almost certain that this is some issue with the list being allocated in different memory regions for each thread, but I simply know far too little about about BackgroundWorker and MultiThreading in general to diagnose it myself.
Any help would be appreciated.
Before you start using more expensive options such as locking or thread safe collections. Try out Threading Tasks. If they work then you have some kind of a problem with your BackgroundWorker, if they don't then your code touches the list somewhere and you'll have to trace that.. (I just think Tasks are so much easier to work with)
private void bw_DoWork()
{
Task.Factory.StartNew(
() =>
{
var r = new Random();
for (int j = 1; j <= 100; j++)
{
for (int i = 0; i < 1000; i++)
{
tempList.Add(r.Next(100));
}
//the rest of whaterver you're doing...
}
});
}
#Stephen Marsh like #Douglas said you need to wait until work is finish.
See this:
// this execute the DoWork asynchronously.
bw.RunWorkerAsync();
// asynchronously means the next line may be executed
// before the DoWork fill the list. In fact can be executed
// before the DoWork begin.
MessageBox.Show("Without wait: " + tempList.Count.ToString());
To correct you can add this line before call RunWorkerAsync:
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
and put this in any place of the MainWindows class.
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Completed: " + tempList.Count.ToString());
}
In my tests always the result was:
"Without wait: 0"
"Completed: 100000"