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!);
}
Related
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
Below is a function I have running in a while(true) loop in a thread running a Winforms GUI.
I have a button set to put text data into the inBuffer object. this always works, however when I place into the buffer object from a different thread, the data is detected, pulled, and printed out in the Console.out.WriteLine statement, however it never shows up in the Display (textBox) object
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != "")
{
Console.Out.WriteLine("FOUND SOMETHING IN BUFFER: " + strDisplayMe);
char[] DisplayMeArr = strDisplayMe.ToCharArray ();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
this.display.Text += DisplayMeArr [i];
Thread.Sleep (100);
}
this.display.Text += '\n';
}
}
EDIT: this is a separate class from what is feeding it data through the static buffer objects
Only the main window thread can access/change controls... if you want update the UI from the thread that runs the loop, you need to sync the call with the main thread using the Control.Invoke method.
For instance...
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != string.Empty)
{
char[] DisplayMeArr = strDisplayMe.ToCharArray();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
this.UpdateUI(() => { this.display.Text += DisplayMeArr[i]; });
Thread.Sleep(100);
}
this.UpdateUI(() => { this.display.Text += '\n'; });
}
}
private void UpdateUI(Action handler)
{
this.Invoke(handler);
}
You can use MethodInvoker to access TextBox from other thread then GUI thread. Make a method to access the TextBox using MethodInvoker. you are assigning text multiple times consider assigning it once for performance, and use StringBuilder for string concatenation.
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != "")
{
Console.Out.WriteLine("FOUND SOMETHING IN BUFFER: " + strDisplayMe);
char[] DisplayMeArr = strDisplayMe.ToCharArray ();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
AssignToTextBox(this.display, this.display.Text + DisplayMeArr [i]);
Thread.Sleep (100);
}
AssignToTextBox(this.display, this.display.Text + '\n');
}
}
void AssignToTextBox(TextBox txtBox, string value)
{
if(txtBox.InvokeRequired)
{
txtBox.Invoke(new MethodInvoker(delegate { txtBox.text = value; }));
}
}
Edit You can use BeginInvoke instead of Invoke to call it asynchronously. Read more about the Invoke and BeginInvoke in this question.
void AssignToTextBox(TextBox txtBox, string value)
{
txtBox.BeginInvoke(new Action(()=>txtBox.Text = value ));
}
I have one problem. In my project i need for more then 100 threads, that's why i prefer to use ThreadPool. here is a part of code, but in this case i have got a lot of memory usage and my form is very laggy, cuz of A lot of BeginInvoke calls(i suppose).
Is there any solutions for this problem?
public void launch()
{
while (data.Count > 0)
{
string[] part;
if (data.Count> 1000)
{
part = data.Take(1000).ToArray();
data = data.Skip(1000).ToList();
}
else
{
part = data.Take(data.Count).ToArray(); data = data.Skip(1000).ToList();
}
foreach (var input in part)
{
try
{
char splitter = ':';
if (input.Contains(';')) splitter = ';';
string login = input.Split(splitter)[0];
string pass = input.Split(splitter)[1];
EncryptCore ec = new EncryptCore(new byte[15]);
PacketSend ps = new PacketSend(ec, "");
ps._login = login;
ps._password = pass;
ps.Brutted+=ps_Parsed;
ps.Failed+=ps_Failed;
ThreadPool.QueueUserWorkItem(ps.Parse);
}
catch { Interlocked.Increment(ref curr); }
}
Thread.Sleep(1000);
}
data.Clear();
}
private void ps_Brutted(User Account)
{
toGet.Add(Account);
Interlocked.Increment(ref good);
goodLabl.BeginInvoke(new Action(delegate()
{
goodLabl.Text = "Good: " + good;
}));
Update();
}
private void Update()
{
try
{
Interlocked.Increment(ref curr);
progLabel.BeginInvoke(new Action(delegate()
{
progLabel.Text = (double.Parse(curr.ToString()) / double.Parse(max.ToString())).ToString("#%");
}));
progressBar.BeginInvoke(new Action(delegate()
{
progressBar.Text = (double.Parse(curr.ToString()) / double.Parse(max.ToString()) * 100).ToString("#");
}));
checkedLabl.BeginInvoke(new Action(delegate()
{
checkedLabl.Text = "Checked: " + curr + " / " + max;
}));
}
catch { }
}
So you have thousands of tasks, and each time one of them completes you update the UI by scheduling 4 updates on the UI thread. What you might consider doing is having the UI update on a timer-based schedule (try 100ms or 200ms) based on the integers that are updating.
Aside from that, you've got some strange numeric operations going on. You can just cast an int to a double, you don't have to round-trip through a String.
Is there any way i can display a String character by character? like in old RPGs
I've tried this:
string text1 ="this is a text";
for (int i = 0; i < text1.Length; i++)
{
textBox1.Text = "" + text1[i];
}
but it only replaces the last character on the text box.
Try This:
public partial class Form1 : Form
{
int count = 0;
string text1 = "this is a scrolling text";
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
textBox1.ReadOnly = true;
SetTimer(500);
}
private void SetTimer(int milliseconds)
{
timer1.Tick+=new EventHandler(timer1_Tick);
timer1.Interval = milliseconds;
timer1.Start();
}
private void timer1_Tick(Object o, EventArgs e)
{
if (count < text1.Length)
{
textBox1.Text += text1[count];
count++;
}
else
{
timer1.Stop();
button1.Enabled = true;
textBox1.ReadOnly = false;
}
}
}
Output:
The following code should do it:
public Form1()
{
InitializeComponent();
TypeText("this is a text");
}
private void TypeText(string text)
{
textBox1.Clear(); // Make sure the textbox is empty
Thread thread = new Thread(delegate() // Create a new thread which fills the textbox periodically
{
button1.BeginInvoke((MethodInvoker)delegate { button1.Enabled = false; }); // Disables the button
for (int i = 0; i < text.Length; i++)
{
int temp = i; // Cache variable because without this, an 'ArgumentOutOfRange' Exception will be thrown
textBox1.BeginInvoke((MethodInvoker)delegate // Invoke to main thread
{
textBox1.Text += text[temp]; // Fill with next char
});
if (text[temp] != ' ') // This makes sure the user doesn't have to wait the double of the time when there is an empty space for the new character
Thread.Sleep(500); // This will stop the seperate thread for 500ms. Won't block the main thread
}
button1.BeginInvoke((MethodInvoker)delegate { button1.Enabled = true; }); // Reenables the button
});
thread.Start(); // Start the new thread and continue the main thread
}
I'm not sure exactly how that helps you. You might want to add why you are doing this. The issue you are having with your code is that your are not appending to the string. Try this line instead:
textBox1.Text += text1[i];
You need to clear textBox1.Text before you start (set it to "").
A better approach might be to print out the hex values so that non printing characters are easily seen. You can do something like this:
string text1 ="this is a text";
var sb = new StringBuilder();
for (int i = 0; i < text1.Length; i++) {
sb.AppendFormat("{0:X} ", text1[i]);
}
textBox1.Text = sb.ToString();
Reading Jeroen Vannevel makes me thing that you are trying to create a "typewriter" affect. In that case try something like:
string text1 ="this is a text";
textBox1.Text = "";
for (int i = 0; i < text1.Length; i++) {
textBox1.Text += text1[i];
Thread.Sleep(250); // 1/4 sec delay
}
This code was written modeled on the code given. There is an implied expectation that this is run from the main UI thread. This means that when you are sitting in a loop with a Sleep delay your UI will be unresponsive (since you are tying up the main thread). You can overcome this by using the Invoke method on the Dispatcher object (of your App) and running the code on a different thread. You should only Invoke the parts that are owned by the main UI (textBox1) in this case.
textBox1.Text = textBox1.Text + text1[i]
To me it seems that you want to break a string into its characters, there are several ways to do that:
1 - String.GetChar() Method:
http://msdn.microsoft.com/en-us/library/microsoft.visualbasic.strings.getchar%28v=vs.110%29.aspx
2 - String.Chars property:
http://msdn.microsoft.com/en-us/library/system.string.chars%28v=vs.110%29.aspx
TextBox1.Text += MyString.Chars[i];
Then if you want to show them one by one with a delay or something like that, you can use timers as others have suggested.
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);
}
}