Live chart slowness - c#

I am charting live data (ekg) from a vital sign monitor. The monitor outputs 500 data points per second which represent a single ekg waveform. I parse these in a backgroundWorker to display them in a chart without freezing the controls. The typical case lasts about an hour, which equals about 1.8 million data points that I have to display.
Here is the backgroundWorker code:
private void BackgroundWorker1_DoWork (object sender, DoWorkEventArgs e) {
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.WorkerReportsProgress = true;
if (backgroundWorker1.CancellationPending == true) {
e.Cancel = true;
}
for (int i = 0; i <= 100; i++) {
backgroundWorker1.ReportProgress(i);
if (i % 25 == 1) {
Thread.Sleep(500);
}
}
try {
Action action = () =>
FillChartEKG();
Invoke(action);
}
catch {
backgroundWorker1.CancelAsync();
MessageBox.Show("No EKG waveform data availaible to parse. Does your vital sign monitor support export of EKG wavedorm data?", "EKG waveform status", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
And here is the long running method that charts the EKG data points:
public async void FillChartEKG() {
List<AnesthWaveformData> waveformData = AnesthWaveformDatas.CreateWaveformObjects(anestheticRecordNum);
var series1 = new Series {
Name = "EKG",
Color = Color.LawnGreen,
IsVisibleInLegend = false,
IsXValueIndexed = true,
ChartType = SeriesChartType.Line,
};
chartEKG.Series.Clear();
chartEKG.Series.Add(series1);
string timeStamp = DateTime.Now.ToString();
DateTime time = new DateTime();
int counter = 0;
int loopCount = 0;
int loopToStopAt = 0;
//await System.Threading.Tasks.Task.Run(() => {
for (int j = 0; j < waveformData.Count; j++) {
if (waveformData[j].EKGWaveform == String.Empty || waveformData[j].EKGWaveform.StartsWith("32767")) {
continue;
}
counter = counter + 1; //there are 500 data points in each EKG waveform HL7 message; counter is the number of valid EKG waveforms in waveFormData
try {
timeStamp = waveformData[j].VSTimeStamp + "000"; //get first VSTimeStamp in series
string format = "yyyyMMddHHmmssfff";
time = DateTime.ParseExact(timeStamp, format, CultureInfo.InvariantCulture);
}
catch { MessageBox.Show("No waveform data to display", "Waveform data status", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); }
string strEKG = String.Empty;
strEKG = strEKG + "^" + string.Join("^", waveformData[j].EKGWaveform.ToString());
strEKG = strEKG.Remove(0, 1); //remove leading ^
string[] count = strEKG.Split('^');
foreach (string point in count) {
try {
time = time.AddMilliseconds(2);
//await System.Threading.Tasks.Task.Run(() => {
chartEKG.Invoke(new Action(() => series1.Points.AddXY(time, Convert.ToDouble(point) * 0.61)));
//});
}
catch (ArgumentException) { }
loopCount = loopCount + 1;
string timeFormat = "yyyyMMddHHmmssfff";
string timeNow = DateTime.Now.ToString(timeFormat);
if (time == DateTime.ParseExact(DateTime.Now.ToString(timeNow), timeFormat, CultureInfo.InvariantCulture)) {
loopToStopAt = loopCount;
}
}
chartEKG.Invoke(new Action(() => chartEKG.ChartAreas[0].AxisX.Maximum = counter * 500));
}
//});
chartEKG.Invoke(new Action(() => chartEKG.ChartAreas[0].AxisX.ScaleView.Scroll(chartEKG.ChartAreas[0].AxisX.Maximum - Convert.ToDouble(loopToStopAt))));
}
The problem is the chart draws reaalllly slowly. If I remove the async from the method it draws really rapidly (what I want) but freezes the GUI while the drawing is completing. I've tried adjusting the Thread.Sleep in the _DoWork eventhandler but doesn't make an appreciable difference in speed. Any suggestions are appreciated.
Here is a YouTube video that shows the output:
https://youtu.be/CmlIScjowFc
Thanks,
-Will

Related

How to use Progress Bar Form button click events when data is loading in my class

I have a class to load some data from a file and a progress bar form to show the process. My class uses a for loop to load data with a selected buffer size and sets the progress bar value in each loop.
I want to add a cancel and pause button to my form, but when my class starts loading data, the form buttons dont work.
I tried using different threads but they can't have access to same element.
How can I make it so that buttons work when data is loading?
Note: user can select the read type so there are different methods for each type(double,int,byte)
here is my load function:
for (int count = 0; count < (FileSize / LoadBufferSize); count++)
{
if (_check_click == 2)
{
return convertedData;
}
else if(_check_click==1)
{
return new Int16[1];
}
else
{
int j = 0;
for (int i = 0; i < fileContent.Length; i++)
{
try
{
fileContent[i] = br.ReadInt16();
}
catch (EndOfStreamException)
{
loadflag = 1;
contentSize = i;
break;
}
}
for (int k = 0; k < contentSize; k += ReSampleRate)
{
try
{
convertedData[(count * fileContent.Length / ReSampleRate) + j] = fileContent[k];
j++;
}
catch (IndexOutOfRangeException)
{
MessageBox.Show("could not load the file completely");
goto lable;
}
catch
{
MessageBox.Show("something went wrong");
}
}
progress = ((count + 1) * fileContent.Length ) / (FileSize / 100);
barForm.SetBar((int)progress);
}
}
lable:
{
return convertedData;
}
This might be helpful.
public class Form1 : Form
{
private Queue<string> _items = new Queue<string>(new [] { "A", "B", "C" });
private Button _startButton = new Button() { Text = "Start", Top = 8, Left = 4, Height = 24, Width = 100 };
private Button _pauseButton = new Button() { Text = "Pause", Top = 32, Left = 4, Height = 24, Width = 100 };
private bool _paused = false;
public Form1()
{
_startButton.Click += (s, e) => this.Process();
_pauseButton.Click += (s, e) => _paused = true;
this.Controls.Add(_startButton);
this.Controls.Add(_pauseButton);
}
private void Process()
{
if (!_paused && _items.TryDequeue(out string text))
{
Console.WriteLine(text);
this.Invoke(() => this.Process());
}
}
}
The key thing here is that the private void Process() method has a recursive call to itself through the .Invoke method. This keeps running Process so long as there are items in the queue, but it also lets other events occur in the meanwhile, so if someone clicks the Pause button then the Process method will stop running.
There is no for loop. Just a repeating Process method that responds to any changes in state.

Multiple curves on Same zedgraph plot

I am trying to read in data from two serial ports, and plot each curve over time, on the same graph. However, when I do this, it connects the curves. How do I keep the two data sets separate but on the same graph? I've seen a lot of solutions using masterPane, however, when I try to use it, my program says that there is no materpane in zedgraph.
Here is the relevant code:
GraphPane myPane2;
PointPairList Oz1time = new PointPairList();
myPane2 = zedGraphControl2.GraphPane;
myPane2.Title = "Data vs Time Plots";
myPane2.XAxis.Title = "Elapsed Minutes";
myPane2.YAxis.Title = "Ozone Data";
private void UpdateData3(string line)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new UpdateDataDelegate(UpdateData3), new object[] { line });
}
else
{
if (chk_DISPLAY_3.Checked == true)
{
timer3.Interval = (30000);
timer3.Start();
OZ1lastdatatime = DateTime.Now;
count++;
if (count > 7)
{
count = 0;
TextBox_3.Text = "";
TextBox_3.AppendText(line);
}
else
{
TextBox_3.AppendText(line);
}
}
if (chk_SAVE_FILE_3.Checked == true)
{
StoreData3.Write(line);
StoreData3.Flush();
}
if (chk_PLOT_1.Checked == true)
{
string[] blahArray = line.Split(new char[] { ',' });
//string blaharray = Convert.ToDouble(blahArray[2]).ToString("F4");
int column_data = Convert.ToInt32(textBox3.Text);
double oz1 = Convert.ToDouble(blahArray[column_data]);
//TextBox_3.Text = Convert.ToString(oz1);
TimeSpan span = DateTime.UtcNow - startDateTimeOfProgram;
double elapsedMinutes = span.TotalMinutes;
Oz1time.Add(elapsedMinutes,oz1);
zedGraphControl2.AxisChange();
zedGraphControl2.GraphPane.AddCurve("", Oz1time , Color.Blue);
zedGraphControl2.Refresh();
}
}
}
private void UpdateData4(string line)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new UpdateDataDelegate(UpdateData4), new object[] { line });
}
else
{
Console.WriteLine(line);
if (chk_DISPLAY_4.Checked == true)
{
timer4.Interval = (30000);
timer4.Start();
OZ2lastdatatime = DateTime.Now;
count++;
if (count > 7)
{
count = 0;
TextBox_4.Text = "";
TextBox_4.AppendText(line);
}
else
{
TextBox_4.AppendText(line);
}
}
if (chk_SAVE_FILE_4.Checked == true)
{
StoreData4.Write(line);
StoreData4.Flush();
}
if (chk_PLOT_2.Checked == true)
{
string[] blahArray = line.Split(new char[] { ',' });
//string blaharray = Convert.ToDouble(blahArray[2]).ToString("F4");
int column_data = Convert.ToInt32(textBox4.Text);
double oz2 = Convert.ToDouble(blahArray[column_data]);
//TextBox_3.Text = Convert.ToString(oz1);
TimeSpan span = DateTime.UtcNow - startDateTimeOfProgram;
double elapsedMinutes = span.TotalMinutes;
Oz1time.Add(elapsedMinutes, oz2);
zedGraphControl2.AxisChange();
zedGraphControl2.GraphPane.AddCurve("", Oz1time, Color.Green);
zedGraphControl2.Refresh();
}
}
}
The main problem appears to be that you are using the same PointPairList, Oz1time, to create both curves. Instead, try creating two separate PointPairLists, one for each curve.
Some relevant code bits:
PointPairList Oz2time = new PointPairList();
...
Oz2time.Add(elapsedMinutes, oz2);
...
zedGraphControl2.GraphPane.AddCurve("", Oz2time, Color.Green);

UI Freezing and Computation Really Slow

I'm writing a program, that should replace or remove some entries from a logfile.txt.
The code is working fine ( at least for small LogFiles). If i use a big file (like 27 MB) its getting very slow and the UI freeze. I cant click anything.
On Button click i execute this method:
private string delete_Lines(string[] lines, string searchString)
{
for (int i = 0; i < lines.Length; i++)
{
if (lines[i].Contains(searchString))
{
rtbLog.Text += "Deleting(row " + (i + 1) + "):\n" + lines[i] + "\n";
progressBar1.Value += 1;
if (cbDB == true)
{
while (is_next_line_block(lines, i) == true)
{
i++;
rtbLog.Text += lines[i] + "\n";
progressBar1.Value += 1;
}
}
}
else
{
res += lines[i]+"\n";
progressBar1.Value += 1;
}
}
tssLbl.Text = "Done!";
rtbLog.Text += "...Deleting finished\n";
return res;
}
Lines is the array of the logfile i am trying to clean up. every entry is a single row . tssLbl is a notification label and rtbLog is a richTextBox, where i'am tracking which row i am deleting.
is_next_line_block is just another method, which is checking of the next lines are part of the block i want to delete. The params of this method are the whole lines array and the line position.
private bool is_next_line_block(string[] lines, int curIndex)
{
if (curIndex < lines.Length-1)
{
if (lines[curIndex + 1].StartsWith(" "))
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
Have anybody any idea, what is causing that freezes and is slowing down the program? I know, that i could speed my code up by parallelizing it, but i cant imagine, that it takes so long to check up a 27 MB txt file without parallelism.
You have several issues here:
You are reading the whole file in buffer (array of string), I am guessing you are calling File.ReadAllLines(). Reading big files in buffer will slow you down, as well as in extreme case run you out of memory.
You are using += operation for your rich textbox Text property. That is time consuming operation as UI has to render the whole rich text box every time you update the text property that way. Better option is to use string builder to load these text, and update rich text box periodically.
To fix this you need to read the file as stream. Progress can be monitored based on bytes read instead of line position. You can run the read operation async and monitor progression on a timer, as shown in example below.
private void RunFileOperation(string inputFile, string search)
{
Timer t = new Timer();
int progress = 0;
StringBuilder sb = new StringBuilder();
// Filesize serves as max value to check progress
progressBar1.Maximum = (int)(new FileInfo(inputFile).Length);
t.Tick += (s, e) =>
{
rtbLog.Text = sb.ToString();
progressBar1.Value = progress;
if (progress == progressBar1.Maximum)
{
t.Enabled = false;
tssLbl.Text = "done";
}
};
//update every 0.5 second
t.Interval = 500;
t.Enabled = true;
// Start async file read operation
System.Threading.Tasks.Task.Factory.StartNew(() => delete_Lines(inputFile, search, ref progress, ref sb));
}
private void delete_Lines(string fileName, string searchString, ref int progress, ref StringBuilder sb)
{
using (var file = File.OpenText(fileName))
{
int i = 0;
while (!file.EndOfStream)
{
var line = file.ReadLine();
progress = (int)file.BaseStream.Position;
if (line.Contains(searchString))
{
sb.AppendFormat("Deleting(row {0}):\n{1}", (i + 1), line);
// Change this algorithm for nextline check
// Do this when it is next line, i.e. in this line.
// "If" check above can check if (line.startswith(" "))...
// instead of having to do it nextline next here.
/*if (cbDB == true)
{
while (is_next_line_block(lines, i) == true)
{
i++;
rtbLog.Text += lines[i] + "\n";
progressBar1.Value += 1;
}
}*/
}
}
}
sb.AppendLine("...Deleting finished\n");
}
As a follow up to your question on Task.Factory.Start() usage, it's done this way (generally):
// you might need to wrap this in a Dispatcher.BeginInvoke (see below)
// if you are not calling from the main UI thread
CallSomeMethodToSetVisualCuesIfYouHaveOne();
Task.Factory.StartNew(() =>
{
// code in this block will run in a background thread...
}
.ContinueWith(task =>
{
// if you called the task from the UI thread, you're probably
// ok if you decide not to wrap the optional method call below
// in a dispatcher begininvoke...
Application.Current.Dispatcher.BeginInvoke(new Action(()=>
{
CallSomeMethodToUnsetYourVisualCuesIfYouHaveAnyLOL();
}));
}
Hope this helps!
Thanks to everybody for the help, especially loopedcode, That's the working version (Took loopedcode's code and made some edit):
private void RunFileOperation(string inputFile, string search)
{
Timer t = new Timer();
StringBuilder sb = new StringBuilder();
{
rtbLog.Text = "Start Deleting...\n";
}
// Filesize serves as max value to check progress
progressBar1.Maximum = (int)(new FileInfo(inputFile).Length);
t.Tick += (s, e) =>
{
rtbLog.Text += sb.ToString();
progressBar1.Value = progress;
if (progress == progressBar1.Maximum)
{
t.Enabled = false;
tssLbl.Text = "done";
}
};
//update every 0.5 second
t.Interval = 500;
t.Enabled = true;
// Start async file read operation
if (rbtnDelete.Checked)
{
if (cbDelete.Checked)
{
System.Threading.Tasks.Task.Factory.StartNew(() => delete_Lines(inputFile, search, ref progress, ref sb, ref res1));
}
}
else
{
//..do something
}
private void delete_Lines(string fileName, string searchString, ref int progress, ref StringBuilder sb, ref StringBuilder res1)
{
bool checkNextLine=false;
using (var file = File.OpenText(fileName))
{
int i = 0;
while (!file.EndOfStream)
{
i++;
var line = file.ReadLine();
progress = (int)file.BaseStream.Position;
if (line.Contains(searchString))
{
sb.AppendFormat("Deleting(row {0}):\n{1}\n", (i), line);
checkNextLine = true;
}
else
{
if (cbDB && checkNextLine && line.StartsWith(" "))
{
sb.AppendFormat("{0}\n", line);
}
else
{
checkNextLine = false;
res1.AppendLine(line);
}
}
}
}
sb.AppendLine("\n...Deleting finished!);
}

How to update button text after event

I'm trying to let a program post a bunch of text. The user enters text, the amount of messages and how fast these must be delivered. While the program is busy, the button text needs to be "Stop" instead of "Start". When you press the button to force it to stop after you've initially launched it, the text changes back to "Start", but this doesn't happen when the program stops after the given amount of messages are delivered, even though the code is in place and doesn't generate an error.
I have a feeling that this is because of the text not updating for some reason. I've tried to flush it with Invalidate() and Update(), but this isn't working. How to fix this?
Here is the code:
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "Start")
{
isEvil = true;
button1.Text = "Stop";
Thread t = new Thread(StartTyping);
t.Start(textBox1.Text);
}
else
{
isEvil = false;
button1.Text = "Start";
}
}
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
//////This does NOT work
//button1.Text = "Start";
//button1.Invalidate();
//button1.Update();
//button1.Refresh();
//Application.DoEvents();
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
}
You have four answers telling you to update UI stuff from the UI thread, but none of them address the logic flow problem with your code.
The reason why it doesn't happen is because it only happens in the for-loop when isEvil is false. When does isEvil get set to false? Only when you click "Stop", and nowhere else.
If you want the button to go back to "Start" after the thread finishes, without clicking "Stop", then you need to add code after the loop to do that, independent of the value of isEvil: (piggybacking off of VoidMain's answer)
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
if (button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
if (button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
}
Now you have duplicated code, so you might want to split it off into a separate method.
You need to be on the UI thread to update the UI.
Try something called the SynchronizationContext. There are plenty of examples when you google it.
If you're in WPF or Silverlight, you could use the Dispatcher. Again, lots of examples if you search those keywords in google or StackOverflow.
You must update your controls from the UI thread. This is how you would do it for winforms.
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
button1.Invoke(new Action(() => button1.Text = "Start"));
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
This will block till button1 get's its text updated. If you don't want it to block, replace Invoke with BeginInvoke
Your best bet is to use a BackgroundWorker. It's a bit too wieldy to add a concise example here but there's a decent tutorial from O'Reilly
Something like this (not tested) should work:
private void StartTyping(object obj)
{
string message = obj.ToString();
int amount = (int)numericUpDown2.Value;
Thread.Sleep(3000);
for (int i = 0; i < amount; i++)
{
if (isEvil == false)
{
if(button1.InvokeRequired)
{
button1.BeginInvoke( new Action(() => { button1.Text = "Start"; }) );
}
else
{
button1.Text = "Start";
}
break;
}
SendKeys.SendWait(message + "{ENTER}");
int j = (int)numericUpDown1.Value * 10;
Thread.Sleep(j);
}
}

multithreading webBrowser does not work

I am trying to code a multithreaded WebBrowser application.
The WebBrowser elements will just navigate to a given URL,
wait until it loads and then
click a button or
submit the form.
This should happen in a loop forever.
I'm using Microsoft Visual Studio 2010 and Windows Forms
Here's my code:
//added windows form 30 webbrowser object
//and now assigning them to an webbrowser array
wbList[0] = webBrowser1; wbList[1] = webBrowser2; wbList[2] = webBrowser3;
wbList[3] = webBrowser4; wbList[4] = webBrowser5; wbList[5] = webBrowser6;
wbList[6] = webBrowser7; wbList[7] = webBrowser8; wbList[8] = webBrowser9;
//etc. until:
wbList[29] = webBrowser30;
for (int i = 0; i < 30; i++)
{
wbList[i].ScriptErrorsSuppressed = true;
wbList[i].NewWindow += new CancelEventHandler(wb_NewWindow);
}
//********************************** creating threads here
Thread[] AllThread = new Thread[100];
int irWhichWbb = 0;
for (int nn = irDirectPostCount; nn < irNumber+1; nn++)
{
AllThread[nn] = new Thread(new
ParameterizedThreadStart(this.MultiThreadWebBrowser));
AllThread[nn].Start(nn.ToString() + ";" + irWhichWbb.ToString());
irWhichWbb++;
}
Application.DoEvents();
for (int nn = 0; nn < irNumber+1; nn++)
{ AllThread[nn].Join(); }
//Multi thread function
void MultiThreadWebBrowser(object parameter)
{
string srParam = parameter.ToString();
int i = Convert.ToInt32 (srParam.Substring(0,(srParam.IndexOf(";"))));
int irWhichWb = Convert.ToInt32(srParam.Substring(srParam.IndexOf(";")+1));
string hdrs = "Referer: http://www.xxxxxxxxx.com/xxxxxxxxxx.aspx";
try
{
wbList[irWhichWb].Navigate(srVotingList[i, 0], "_self", null, hdrs);
}
catch { }
try { waitTillLoad(irWhichWb); }
catch { }
waitTillLoad3();
}
// wait until webbrowser navigate url loaded
private void waitTillLoad(int irWhichLoad)
{
WebBrowserReadyState loadStatus;
//wait till beginning of loading next page
int waittime = 100000;
int counter = 0;
while (true)
{
try
{
loadStatus = wbList[irWhichLoad].ReadyState;
Application.DoEvents();
if ((counter > waittime) ||
(loadStatus == WebBrowserReadyState.Uninitialized) ||
(loadStatus == WebBrowserReadyState.Loading) ||
(loadStatus == WebBrowserReadyState.Interactive))
{
break;
}
counter++;
}
catch { }
}
//wait till the page get loaded.
counter = 0;
while (true)
{
try
{
loadStatus = wbList[irWhichLoad].ReadyState;
Application.DoEvents();
if (loadStatus == WebBrowserReadyState.Complete)
{
break;
}
if (counter > 10000000)
break;
counter++;
}
catch { }
}
}
private void waitTillLoad3()
{
DateTime dtStart = DateTime.Now;
while (true)
{
if ((DateTime.Now - dtStart).TotalMilliseconds > 4000)
break;
Application.DoEvents();
}
}
You don't say what kind of failure you get: "doesn't work" is not a good description.
I would first try with just a single thread. Does that work?
You have empty catch blocks, so you are silently ignoring some error conditions. This may well by hiding a problem.

Categories

Resources