I'm working a on project that continuously listens for packets on UDP port 514 (syslog). When a packet is received I need to parse the message so it can be displayed on my main form in a DataGridView control.
I have it so it updates the DataGridView with new messages, but I could get over 100 syslog messages for the same thing from my firewall so I want to search my dataGrid, see if the message already exists, and then update a "Hit count" column instead of making a new row for duplicate messages.
The problem is that I can't figure out how to invoke my DataGridView in a foreach loop. I'm not returning the value from the thread, I'm processing the data as it comes in, so I need to invoke the DataGridView control from the UI thread.
My code so far:
private async void Form1_Load(object sender, EventArgs e)
{
var udpCap = await Task.Run(() => captureSyslog());
}
public async Task<string> captureSyslog()
{
var listener = new UdpClient(514, AddressFamily.InterNetwork);
var ep = default(IPEndPoint);
while (true)
{
try
{
var data = listener.Receive(ref ep);
string incomingSyslog = Encoding.ASCII.GetString(data);
outputMessage(incomingSyslog);
}
catch (Exception ex2)
{
outputMessage(ex2.ToString());
}
}
}
private void outputMessage(string txt)
{
string[] chopped = txt.Split(',');
string descrip = chopped[32];
string severity = chopped[34];
string paloalto = chopped[59];
int rowIndex = -1;
foreach (DataGridViewRow row in syslogOutput.Rows)
{
try
{
if (row.Cells[2].Value.ToString().Equals(descrip) | row.Cells[1].Value.ToString().Equals(paloalto))
{
//Code to update DataGridView
}
}
catch (Exception ex3){ debugLabel1.Invoke(new Action(() => debugLabel1.Text = ex3.ToString()));}
}
if (syslogOutput.InvokeRequired)
{
syslogOutput.Invoke(new Action(() => syslogOutput.Rows.Add("1", chopped[59], chopped[34], chopped[32])));
}
}
When the code is run like this, I get a "System.NullReferenceException: Object reference not set to an instance of an object" which makes sense because the "syslogOuput" DataGridControl doesn't exist in the thread that's calling it. However the "syslogOutput.Invoke" line farther down works because it's being pulled in from the main UI thread.
Additionally, I could update my datagrid with the udpCap var in the Form1_load method, but when I do that I just get one message and the Task.Run thread quits.
So I guess my question can be phrased two ways:
How can I invoke a control in a foreach loop?
and/or
How can I get a return value from a task without ending the task that's returning the value?
UPDATE:
I dumped the Task and used the BackgroundWorker feature to capture the syslog messages. I abused the UserState in the ProgressChanged method to pass the syslog string so I can parse and then display it to my DataGridView natively in the UI thread. As far as I can tell the BackgroundWorker thread will run forever if there isn't a cancellation and/or the ReportProgress method never receives a "100" indicating that the process is 100% complete.
New code:
namespace ASA_SyslogCapture
{
public partial class Form1 : Form
{
BackgroundWorker bgWorker;
public Form1()
{
InitializeComponent();
bgWorker = new BackgroundWorker();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged += new ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.WorkerReportsProgress = true;
}
private async void Form1_Load(object sender, EventArgs e)
{
bgWorker.RunWorkerAsync();
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
//put udp code here
var listener = new UdpClient(514, AddressFamily.InterNetwork);
var ep = default(IPEndPoint);
while (true)
{
try
{
var data = listener.Receive(ref ep);
string incomingSyslog = Encoding.ASCII.GetString(data);
bgWorker.ReportProgress(0,incomingSyslog);
}
catch (Exception ex2) { debugLabel1.Text = ex2.ToString(); }
}
}
void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
debugLabel1.Text = e.UserState as String; //this will eventually be my datagridview code
}
}
}
Huge thanks to this thread for the suggestion to use UserState to pass the string: C# backgroundWorker reports string?
Related
First I'd like to say sorry for doubleposting. I'm afraid the context and questions I posted before didn't clearify it enough and although one of the solutions worked I am still struggling to grasp the concept, that's why I decided to make a new one. Also because there are apparently many different opinions of how it should be done in the right manner.
First here is a clean running code example that I`m using to figure this out:
namespace serialInterfaceTest
{
public partial class Form1 : Form
{
string serialDataIn;
bool sent = false;
public Form1()
{
InitializeComponent();
serialPort.PortName = "COM3";
serialPort.BaudRate = 9600;
serialPort.Open();
}
private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
serialDataIn = serialPort.ReadExisting();
this.Invoke(new EventHandler(saveData));
}
public void saveData(object sender, EventArgs e)
{
string trimmedMsg = serialDataIn;
richTextBox.Text += trimmedMsg;
if(trimmedMsg.Contains("*")) button_sendMsg.Enabled = true;
}
private void richTextBox_TextChanged(object sender, EventArgs e)
{
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
}
private void button_sendMsg_Click(object sender, EventArgs e)
{
send(textBox_message.Text);
button_sendMsg.Enabled = false;
//WAIT FOR RESPONSE BEFORE ALLOWING THE USER TO SEND ANOTHER MESSAGE
}
private void button_loopMsg_Click(object sender, EventArgs e)
{
button_loopMsg.Enabled = false;
for (int i = 0; i < 10; i++)
{
send(textBox_message.Text);
//WAIT FOR RESPONSE BEFORE CONTINUING THE LOOP
}
button_loopMsg.Enabled = true;
}
void send(String message)
{
serialPort.Write(message);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort.IsOpen)
{
try
{
serialPort.Close();
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
}
}
}
It is a very simple GUI with a richTextBox to receive messages, a textbox to enter a message and two buttons that will send the message in two different ways.
The first case, where the user is sending the message manually is simple. Just deactivate the button so you can't spam it. The second case where I'm trying to send multiple messages in a loop seems way more complicated than I initially thought.
I try to outline my needs:
richTextBox always get's updated when a message arrives over serial
when user want's to send a message he has to wait for a reply before sending the next
when sending messages consecutively, the method for sending the message need's to stop and wait for a reply before proceeding with
the next message or send a timeout and stop the execution of the
method
answers from the serial port always end with a '*'
an answer can either resume a loop as described above or trigger a method from my GUI
all communication on serial is in ASCII.
GUI should stay responsive
Using the localHandler in the approved solution from my other question works and for one simple case that's fine but I quickly realized that it is not flexible enough. I tried to figure it out but didn't get anywhere.
In my code example that I posted above, I separated the serialPort.Write in it's own method. I'm thinking something in the terms of this:
UI is running in it's own thread and serialPort_DataReceived is running in it's own thread, which it is doing anyways as my reasearch showed. So now when I'm receiving data everything is fine, the UI gets updated everytime I receive a message from the serial port. Now for the sending part I guess the best way is to give it it's own thread as well? So I can simply pause the thread where my message is being sent, wait for a reply on the main thread and then continue. Or which other concept would fulfill my need here?
I'm new to object-oriented programming, most of the stuff I have done so far is C based so I could use any help here. Thanks for considering and again, sorry for the double post. I just hope my question is more clear now.
After 4 days of almost no sleep I figure it out and want to post the solution to anybody who is trying to have some sort of a flow control in their serial communication. In the end I was using async methods. I think this is as simple as it can get for somebody who doesn't have a lot of C# experience. Here is the code for the form:
namespace serialInterfaceTest
{
public partial class Form1 : Form
{
string serialDataIn;
public Form1()
{
InitializeComponent();
serialPort.PortName = "COM3";
serialPort.BaudRate = 9600;
serialPort.Open();
}
private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
serialDataIn = serialPort.ReadExisting();
this.Invoke(new EventHandler(saveData));
}
public void saveData(object sender, EventArgs e)
{
string trimmedMsg = serialDataIn;
richTextBox.Text += trimmedMsg;
}
private void richTextBox_TextChanged(object sender, EventArgs e)
{
richTextBox.SelectionStart = richTextBox.Text.Length;
richTextBox.ScrollToCaret();
}
private async void button_sendMsg_Click(object sender, EventArgs e)
{
button_sendMsg.Enabled = false;
await Task.Run(() => send(textBox_message.Text));
button_sendMsg.Enabled = true;
//WAIT FOR RESPONSE BEFORE ALLOWING THE USER TO SEND ANOTHER MESSAGE
}
private async void button_loopMsg_Click(object sender, EventArgs e)
{
button_loopMsg.Enabled = false;
for (int i = 0; i < 10; i++)
{
await Task.Run(() => send(textBox_message.Text));
//WAIT FOR RESPONSE BEFORE CONTINUING THE LOOP
}
button_loopMsg.Enabled = true;
}
private async Task send(String message)
{
serialDataIn = "";
serialPort.Write(message);
while (!serialDataIn.Contains("*"))
{
//PROCESS ANSWERS HERE
serialDataIn = serialPort.ReadExisting();
if (serialDataIn.Contains("*"))
{
this.Invoke(new EventHandler(saveData));
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort.IsOpen)
{
try
{
serialPort.Close();
}
catch (Exception error)
{
MessageBox.Show(error.Message);
}
}
}
}
}
I have the async method send data, and the two buttons are async as well. When I press them I'm just waiting for the task to complete before another input is allowed. I think this should be a good starting point for other projects as well. UI stays responsive, messages don't get queued up. The richTextBox on the UI get`s updated via Invoke so messages are displayed as soon as they arrive.
Here is the test code for an arduino:
#define println Serial.println
char serialIn;
String appendSerialData;
void setup()
{
Serial.begin(9600);
}
void loop()
{
appendSerialData = "";
while (Serial.available() > 0)
{
serialIn = Serial.read();
appendSerialData += serialIn;
}
/* asterisk functions as message identifier */
delay(1000);
if (appendSerialData != "") println(appendSerialData + " *");
for (int i = 0; i < 5; i ++)
{
println(i);
}
delay(100);
}
If there are any improvements I can make to this I`m happy to hear about it.
I am working on a Windows form application, where I send data through buttons, and continuously receive data for updating some gauge values.
The trouble I am facing, is when reading the serial port, if data is coming too fast, my application responds very slow, for example I can have a data rate of 1000Hz when reading.
I have attached a simplified version of my code.
What could cause this application to hang? I read that I need some separate thread? But how to achieve this?
namespace Motor
{
public partial class Form1 : Form
{
SerialPort ComPort = new SerialPort();
string InputData = String.Empty;
delegate void SetTextCallback(string text);
public Form1()
{
InitializeComponent();
ComPort.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
InputData = ComPort.ReadExisting();
if (InputData != String.Empty)
{
this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
}
}
private void SetText(string text)
{
if (text.Contains("CM"))
{
// process the text content here
}
}
}
}
This is most likely your issue
this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
If your serial port is thrashing, its trying to marshal back to the UI thread all the time. Do you really need to update the main UI thread this fast? cant you just do it every second or periodically
In regards to the statement
What could cause this application to hang? I read that I need some
separate thread? But how to achieve this?
SerialPort.DataReceived Event
The DataReceived event is raised on a secondary thread when data is
received from the SerialPort object
I.e it is already running on secondary thread, so starting another thread is not going to help you
Update
In reference to your comment
1000hz is the data rate coming from the hardware, its no need for the
GUI to be that fast, but i want the gauges to move smooth.
You can still make them smooth, still at least preventing UI throttling would be your key, try buffering for 200-300 milliseconds or so. I.e just play around with it
Use ReadLine to avoid a chunk Data Recieved.
namespace Motor
{
delegate void SetTextCallback(string text);
public partial class Form1 : Form
{
SerialPort ComPort = new SerialPort();
string InputData = String.Empty;
SetTextCallback st;
public Form1()
{
InitializeComponent();
st = new SetTextCallback(SetText);
}
private void Form1_Load(object sender, EventArgs e)
{
OpenPort();
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
InputData = ComPort.ReadLine();
if (InputData != String.Empty)
{
this.Invoke(st,new object[] { InputData});
}
}
private void SetText(string text)
{
Console.WriteLine(text);//To check the return value
if (text.Contains("CM"))
{
// process the text content here
}
}
}
}
There's a lot of return value in this Part InputData;
List of Return Value (Be careful to check this value)
1. OK
2. >
3. + CMGS: ID_NUMBER_HERE
4. ERROR ERROR_NUMBER_HERE
And any others...
Now its's depends on your code when you want to let go or continue the code with that values return.
off course before this code you already opened the PORT. Coz this code doesn't work anymore if the PORT is closed.
GUIDE TO OPEN A PORT
private void OpenPort()
{
private System.IO.Ports.SerialPort ComPort;
ComPort= new System.IO.Ports.SerialPort("COM1");//depends on your
Device COM
ComPort.NewLine = System.Environment.NewLine;
ComPort.BaudRate = 115200;
ComPort.Parity = System.IO.Ports.Parity.None;
ComPort.DataBits = 8;
ComPort.StopBits = System.IO.Ports.StopBits.One;
ComPort.Handshake = System.IO.Ports.Handshake.None;
ComPort.ReadTimeout = 3600000;
ComPort.WriteTimeout = 3600000;
ComPort.Encoding = Encoding.GetEncoding("iso-8859-1");
GC.SuppressFinalize(ComPort);
SerialPortFixer.Execute("COM1");
ComPort.DataReceived += new
System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
ComPort.Open();
GC.SuppressFinalize(ComPort.BaseStream);
ComPort.DtrEnable = true;
ComPort.RtsEnable = true;
}
Hope it helps.
I do not have any example code which is written but i can suggest you to use threads or background workers to read data, it is really easy to understand useage of background workers or threads. You can watch short tutorials. Try to create a button which starts background worker and with that background worker, you can read your data without freezing. Sorry i do not have time to write example but in my opinion these are the topics you should be focusing on (Bakcground workers or Threads)
I hope this helps.
try implement async await
more info:
https://learn.microsoft.com/en-us/dotnet/csharp/async
https://msdn.microsoft.com/en-us/library/hh156513(v=vs.120).aspx
Here's a quick example (although I can't verify it because I don't have the necessary requirements for running the code, but should give an idea):
namespace Motor
{
public partial class Form1 : Form
{
SerialPort ComPort = new SerialPort();
string InputData = String.Empty;
delegate void SetTextCallback(string text);
public Form1()
{
InitializeComponent();
ComPort.DataReceived +=
new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
}
private async void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
var size = ComPort.ReadBufferSize;
var data = new byte[size];
await ComPort.BaseStream.ReadAsync(data,0,size);
InputData += ComPort.Encoding.GetString(data);
SetText(InputData);
}
private void SetText(string text)
{
if (text.Contains("CM"))
{
// process the text content here
}
}
}
}
My program has to test several products in different slots simultaneously. When there's an error in a slot like accidental detachment from the computer, the program is suppose to log the error type and the serial number of the product that has been provided by the user when starting up the UI into a textfile.
I'm using Background Worker to handle the multi-threading. While I have managed to log the error type using e.Error, I can't seem to figure out how to pass the serial number from the DoWork function to the Background Worker error handler.
I tried googling for a solution but it seems like nobody has asked this before. I will really appreciate any help given.
PS: I'm quite new to C# so be gentle haha :)
Below is an example code:
private void startAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation == true)
{
// Cancel the asynchronous operation.
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
int b = 0; //simulate error
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
string[] array2 = { "1", "cancelled" };
e.Result = array2; //passing values when user cancel through e.Result object
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
worker.ReportProgress(i * 10, "Test a");
int a = 1 / b; //simulate error
System.Threading.Thread.Sleep(1000);
}
string[] array1 = {"1","done"};
e.Result = array1; //passing values when complete through e.Result object
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = e.ProgressPercentage.ToString() + "%" + e.UserState.ToString();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
string[] someArray2 = e.Result as string[];
string sernum = someArray2[0];
string status = someArray2[1];
resultLabel.Text = sernum + " " + status;
}
else if (e.Error != null)
{
resultLabel.Text = "Error: " + e.Error.Message; //how to pass sernum here?
}
else
{
string[] someArray = e.Result as string[];
string sernum = someArray[0];
string status = someArray[1];
resultLabel.Text = sernum + " " + status;
}
}
There are lots of different ways to get data back to the RunWorkerCompleted event handler in the case of an exception.
IMHO, the most natural from a semantic point of view is to put the data in the exception itself. For example:
class BackgroundWorkerException : Exception
{
public string Sernum { get; }
public BackgroundWorkerException(string sernum, Exception inner)
: base("DoWork event handler threw an exception", inner)
{
Sernum = sernum;
}
}
Then in your DoWork handler:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
try
{
int b = 0; //simulate error
for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
string[] array2 = { "1", "cancelled" };
e.Result = array2; //passing values when user cancel through e.Result object
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
worker.ReportProgress(i * 10, "Test a");
int a = 1 / b; //simulate error
System.Threading.Thread.Sleep(1000);
}
string[] array1 = {"1","done"};
e.Result = array1; //passing values when complete through e.Result object
}
}
catch (Exception e)
{
throw new BackgroundWorkerException("1", e);
}
}
Finally, in the RunWorkerCompleted event handler:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == true)
{
string[] someArray2 = e.Result as string[];
string sernum = someArray2[0];
string status = someArray2[1];
resultLabel.Text = sernum + " " + status;
}
else if (e.Error != null)
{
string sernum = ((BackgroundWorkerException)e.Error).Sernum;
resultLabel.Text = "Error: " + e.Error.Message;
}
else
{
string[] someArray = e.Result as string[];
string sernum = someArray[0];
string status = someArray[1];
resultLabel.Text = sernum + " " + status;
}
}
Your question isn't clear about what sernum actually represents, and in particular whether it's a single value for a given background task, or a single task could have more than one value for sernum. If it's the former, i.e. you know when you start the task what the value is, then you could pass it directly to the event handlers by capturing it in an anonymous method used for each actual event handler.
That approach won't work though in your specific scenario without some changes. You appear to have added a single BackgroundWorker object to your form as a component and are reusing it. Using an anonymous method works better/more easily if you are creating a new BackgroundWorker each time, so that you can subscribe your anonymous method delegate to DoWork and RunWorkerCompleted. (You have to subscribe it just before each invocation because, presumably, the sernum value is different each time.)
You could get it to work with the single component added to the form in the Designer as you're doing here, but it's a lot more complicated because you have to dynamically add a handler to the RunWorkerCompleted event which unsubscribes both itself and the delegates you subscribed to the DoWork and RunWorkerCompleted events (you wouldn't subscribe any methods directly to the component in the Designer, in this scheme).
Another alternative is to create a custom data structure passed as the argument for RunWorkerAsync(), which can contain a property for the sernum value. You can set this value in the method that starts the worker, or in the DoWork event handler.
This approach fits only a little better with the component-in-Designer scenario you have, because you still need a way to get the reference to that custom data structure back to the RunWorkerCompleted event handler, which you can do only by storing it in e.g. an instance field that can be shared between the Click event handler that starts the worker and the RunWorkerCompleted event (and frankly, if you do that, at that point it's debatable whether it's even worth it to pass that reference to the RunWorkerAsync() method, since the DoWork event handler could get at the same instance field just as well.)
Another alternative is to catch the exception as I've done in my code example above, but then instead of rethrowing the exception, treat it as if the work was cancelled (i.e. set the Result and Cancel properties).
Yet another approach is to abandon BackgroundWorker altogether and switch to the TPL Task-based idiom. That doesn't solve the problem implicitly, but it allows any of the above options, as well as the option of just defining your own mode for passing errors back.
If you need more specific help than that, you'll need to post a new question, with a good Minimal, Complete, and Verifiable code example that shows which of the above approaches you've attempted to try, or some other alternative not listed here, and what specifically you're unable to figure out.
See code below. You don't need a class. Can simple send a string or int using similar code.
public class Parameters
{
public string message = "";
}
private void startAsync_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
// Start the asynchronous operation.
Parameters parameters = new Parameters() { message = "The quick brown fox jumped over the lazy dog" };
backgroundWorker1.RunWorkerAsync(parameters);
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Parameters parameters = e.Argument as Parameters;
}
I've looked in many places for this but still haven't found a solution. What I'm trying to achieve is being able to use BackgroundWorker on a timed basis. Here's an example:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.DoWork += isOnline;
isDbAvail.RunWorkerCompleted += rewriteOnlineStatus;
}
private void rewriteOnlineStatus(object sender, RunWorkerCompletedEventArgs e)
{
Subs.Connection connection = new Subs.Connection();
changeStatus(connection.isDbAvail());
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
System.Threading.Thread.Sleep(8000);
}
}
public void changeStatus(bool status)
{
if (status)
{
serverStatusVal.Text = "Connected";
serverStatusVal.ForeColor = System.Drawing.Color.DarkGreen;
}
else
{
serverStatusVal.Text = "Not connected";
serverStatusVal.ForeColor = System.Drawing.Color.Red;
}
}
What's happening here is that the isOnline method checks if there is a connection to the database (just an example) every 8 seconds and changes the text accordingly. What I've noticed though, is that the while loop inside the isOnline method causes the rewriteOnlineStatus method never to fire because it runs indefinitely. Is there another workaround to this?
I suggest you use BackgroundWorker.ReportProgress, and check connectivity in the background thread.
Something like this:
public Main()
{
isDbAvail = new BackgroundWorker();
isDbAvail.WorkerReportsProgress = true;
isDbAvail.DoWork += isOnline;
isDbAvail.ProgressChanged += rewriteOnlineStatus;
isDbAvail.RunWorkerAsync();
}
private void rewriteOnlineStatus(object sender, ProgressChangedEventArgs e)
{
changeStatus((bool)e.UserState);
}
private void isOnline(object sender, DoWorkEventArgs e)
{
while (true)
{
Console.WriteLine("Checking database connection");
Subs.Connection connection = new Subs.Connection();
isDbAvail.ReportProgress(0, connection.isDbAvail);
System.Threading.Thread.Sleep(8000);
}
}
Now the BackgroundWorker is doing the work, and reporting back to the UI thread via ProgressChanged.
I have created a thread to perform certain functionality in my application and while performing it I want to update the label in the main form of the application which is visible to user.
I tried to return the string data through the function which I am calling usinag seprate thread but it does not work.
Please let me know if there is any solution to update the label text while performing an activity using thread.
class e2ertaData : e2erta1
{
public void rsData()
{
network networkDetails = new network();
csv csvFile = new csv();
ftpFile ftpData = new ftpFile();
//Host Geo Data
string getIP = networkDetails.GetIP();
string[] hostData = getIP.Split('~');
GeoIP geoIPReq = new GeoIP();
GeoIpData geoIPReqData = new GeoIpData();
geoIPReqData = geoIPReq.GetMy();
if (geoIPReqData.KeyValue["Error"].ToString() == "NO")
{
//Reading server names from XML file
XmlDocument thisXmlDoc = new XmlDocument();
thisXmlDoc.LoadXml(ftpData.getConfigFile("server.xml"));
XmlNodeList xnList = thisXmlDoc.SelectNodes("/servers/server");
//updating label in e2erta1
this.l1.Text = "daaaaaaaaaaa";
this.l1.Visible = true;
this.l1.Refresh();
foreach (XmlNode xn in xnList)
{
string rtNote = "";
string requestedServer = xn["sname"].InnerText;
string rtGet = networkDetails.GetRT(requestedServer);
if (rtGet.Contains("Exception"))
{
rtNote = rtGet;
//MessageBox.Show(rtNote);
}
try
{
var row = new List<string> { rtGet, rtNote };
ftpData.addToCSVFile(row);
}
catch (Exception c)
{
MessageBox.Show(c.ToString());
}
}
}
else
{
MessageBox.Show("Geo data : " + geoIPReqData.KeyValue["Error"].ToString());
}
//return null;
}
}
Thanks,
Naveed
Also consider using BackgroundWorker component.
Drag BackgroundWorker from ToolBox to your form
Set backgroundworker's property WorkerReportsProgress to true
Subscribe to backgroundworker's event DoWork
Subscribe to backgroundworker's event ProgressChanged
Inside DoWork event handler run all what you do in your thread, and call ReportProgress method to pass progress to your form:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// reading server names from XML file
for (int i = 0; i < xnList.Count; i++)
{
XmlNode xn = xnList[i];
// process node
// report percentage to UI thread
int percentProgress = (i+1)*100/xnList.Count;
backgroundWorker1.ReportProgress(percentProgress);
}
}
Inside ReportProgress event handler simply assign value to label:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = e.ProgressPercentage.ToString();
}
To start background processing call backgroundWorker1.RunWorkerAsync();
UPDATE: Your code is not working, because controls could be updated only from thread which created them (UI thread). So you should use Invoke to execute update functionality on UI thread. Example and description you can find here.
Use this from your thread:
this.Invoke((MethodInvoker)delegate
{
label.Text = "...";
});
Edit:
You can also test the IsHandleCreated property before using Invoke:
private void UpdateLabel(string text)
{
if (this.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate
{
label.Text = text;
});
}
else
{
label.Text = text;
}
}