I've been trying to make a program to split larger text files into smaller pieces so they are easier to work with.
I currently have two issues and cannot figure out what is going on.
Issue 1: The background worker will sometimes fire multiple times. I cannot seem to figure out why or how many times it decides to run. It will run the split and on the final file it seems like it loops back to the beginning of do work and run it again. It fires off multiple do work completed tasks as well. To complicate things if I set the number of files split to a different number I can get a different number of times the background worker seems to fire, but it doesn't directly correlate to the number of files. Sometimes the same number of files causes the background worker to fire only once and sometimes it fires multiple times.
Issue 2: Sometimes the split will not create all the files. With some files if I run it it will create the first two files and then drop the rest. Seems to only happen when I set the number to 3 files to split into. if I take the line count and add it up it should equal out correctly. So i'm not sure what is going on there.
Calling Thread
private void StartSplit()
{
if (int.TryParse(NumberOfFilesTB.Text, out _numberOfFiles))
{
if (bg.IsBusy)
{
((MainWindow)Application.Current.MainWindow).SetStatus("Warning",
"Please only run one split process at a time.");
return;
}
((MainWindow)Application.Current.MainWindow).DisplayAlert(
"Split is running, you will receive an alert when it has finished. You may use other tools while the split is running.");
var args = new List<string> { _filepath, _includeHeaders.ToString(), _numberOfFiles.ToString() };
bg.DoWork += bg_DoWork;
bg.WorkerReportsProgress = true;
bg.ProgressChanged += ProgressChanged;
bg.RunWorkerCompleted += bg_RunWorkerCompleted;
bg.WorkerSupportsCancellation = true;
bg.RunWorkerAsync(args);
ProcessText.Text = "Running split process";
}
else
{
((MainWindow)Application.Current.MainWindow).SetStatus("Warning", "Please enter a number for number of files");
}
}
Background Thread
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
var args = e.Argument as List<string>;
string filepath = args[0];
string includeHeaders = args[1];
int numberOfFiles = Convert.ToInt32(args[2]);
int numberOfRows = _lineCount / numberOfFiles;
_tempath = Path.GetDirectoryName(_filepath);
Directory.CreateDirectory(_tempath+"\\split");
if (includeHeaders == "True")
{
using (var reader = new StreamReader(File.OpenRead(filepath)))
{
_lines.Clear();
_header = reader.ReadLine();
_lines.Add(_header);
for (int i = 0; i < _lineCount; i++)
{
if (bg.CancellationPending)
{
e.Cancel = true;
break;
}
int percentage = (i + 1) * 100 / _lineCount;
bg.ReportProgress(percentage);
_lines.Add(reader.ReadLine());
if (i % numberOfRows == 0)
{
_counter++;
Debug.WriteLine(i);
if (i == 0)
{
//skip first iteration
_counter = 0;
continue;
}
_output = _tempath + "\\" + "split\\" + _fileNoExt + "_split-" + _counter + _fileExt;
_filesMade.Add(_output);
File.WriteAllLines(_output, _lines.ConvertAll(Convert.ToString));
_lines.Clear();
_lines.Add(_header);
}
}
}
}
else
{
using (var reader = new StreamReader(File.OpenRead(filepath)))
{
_lines.Clear();
_header = reader.ReadLine();
_lines.Add(_header);
for (int i = 0; i < _lineCount; i++)
{
if (bg.CancellationPending)
{
e.Cancel = true;
break;
}
int percentage = (i + 1) * 100 / _lineCount;
bg.ReportProgress(percentage);
_lines.Add(reader.ReadLine());
if (i % numberOfRows == 0)
{
_counter++;
if (i == 0)
{
//skip first iteration
_counter = 0;
continue;
}
string output = _tempath + "\\" + "split\\" + _fileNoExt + "_split-" + _counter + _fileExt;
_filesMade.Add(_output);
File.WriteAllLines(output, _lines.ConvertAll(Convert.ToString));
_lines.Clear();
}
}
}
}
}
Run Worker Completed
private void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
StopSplit();
_filesMade.Clear();
ProcessText.Text = "Split cancelled";
return;
}
_filesMade.Clear();
ProcessText.Text = "Split has completed, click here to open the directory";
}
I bet your BgW is a member of your class...
In Startsplit() you add a new callback each time this function is executed.
That's why it runs multiple times.
Other answer after dinner.
Finished dinner...
your method of counting is faulty in multiple ways:
1) If you are missing files, I bet it's the last one. E.g. 30 lines, 3 files:
i % numberOfRows is zero at i=0, 10, 20, but i does not reach 30.
2) you are missing lines, e.g. 31 lines 4 files:
Files are saved at i=7, 14, 21, 28. Lines 29-31 are missing.
I suggest you use a nested for loop, outer one for files, inner one for lines, and improve your calculation. And put all your lists and counters inside the function!
I hope you appreciate my answer. I hate typing on a tablet. But also didn't want to start my computer just for this... ;-)
Related
I have a code that export my DatagridCells to .CSV, but I'm having a problem reading it through a MetaTrader Terminal program as the .CSV files were having Blank Space in the 1st line and after that, the .CSV files are either blank some moments and back to normal.
Is it something to do with my code or with DataGridViews?
public void writeCSV2(DataGridView gridIn, string outputFil)
{
if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.MyComputer) + "\\Useful\\"))
{
Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.MyComputer) + "\\Useful\\");
}
if (gridIn.RowCount > 0)
{
string value = "";
DataGridViewRow dr = new DataGridViewRow();//
//write DataGridView rows to csv
for (int j = 0; j <= gridIn.Rows.Count - 1; j++)
{
dr = gridIn.Rows[j];
var oFile = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer)+"\\Useful\\" +dr.Cells[0].Value.ToString() + ".csv";
using (StreamWriter swOut = new StreamWriter(oFile))
{
if (j > 0)
{
swOut.WriteLine();
}
for (int i = 0; i <= gridIn.Columns.Count - 1; i++)
{
if (i > 0)
{
swOut.Write(", ");
}
value = dr.Cells[i].Value.ToString();
//replace comma's with spaces
value = value.Replace(',', ' ');
//replace embedded newlines with spaces
value = value.Replace(Environment.NewLine, "");
swOut.Write(value);
}
swOut.Close();
}
}
}
}
Thanks for help and opinion on this
The blank line you're talking about will be on the top of every file except your first csv file. Remove
if (j > 0)
{
swOut.WriteLine();
}
from your code and there won't be any blank lines on top of your files anymore.
To answer your second question: Use a Timer. Just drag it from your toolbox or add it programmatically:
Timer timer1 = new Timer();
timer1.Interval = 1; // interval property is in milliseconds. use 1 for 1ms, 1000 for 1 second etc.
timer1.Tick += timer1_Tick;
timer1.Enabled = true;
private void timer1_Tick(object sender, EventArgs e)
{
// disable timer to avoid getting triggered while executing method
timer1.Enabled = false;
// run your method
writeCSV2(gridIn, outputFil);
// reenable the timer
timer1.Enabled = true;
}
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!);
}
I already have this but it doesn't work... I need to check if a bit from plc is high or not. If it is still high I need to wait 5 seconds and check again.. Now I am trying to find something so there is some visual feedback for the user. In a textbox I want to put 'waiting...' while the points after waiting increase per 5 seconds. I tried a lot of things but can't seem to get it to work. Mostly of the time's it just hangs 25 seconds without updating the GUI and then it continues... :/
// First check if the plc bit, that says if there is still an order active,
// is still set. If so then we wait a couple seconds.
var bitorder = Main.PIOGetValue(jbmis.IO_GET_INPUT, "BoxManInStarted");
int counter = 1;
string loadingpoints = "";
loadtimer.Tick += timer_Tick;
loadtimer.Interval = (int)TimeSpan.FromSeconds(5).TotalMilliseconds;
loadtimer.Start();
loadtimer.Enabled = true;
// Stopwatch sw = new Stopwatch();
while(bitorder != 0 && loadtimercounter != 25)
{
// TODO multithreaded
#region testcode
// MessageBox.Show("Waiting for previous order to be stopped" + loadingpoints);
// Context.UserMessageService
// .ShowMessage("Waiting for previous order to be stopped" +
// loadingpoints, "Waitingfororder");
// sw.Start();
// while (sw.Elapsed < TimeSpan.FromSeconds(25))
// {
// if (sw.Elapsed.Seconds % 5 == 0)
// {
// loadingpoints = loadingpoints + ".";
// tbScannedEANPick.Background = Brushes.OrangeRed;
// tbScannedEANPick.Text = "Waiting" + loadingpoints;
// }
// }
// sw.Stop();
// loadingpoints = loadingpoints + ".";
// tbScannedEANPick.Background = Brushes.OrangeRed;
// tbScannedEANPick.Text = "Waiting" + loadingpoints;
// tbScannedEANPick.UpdateLayout();
#endregion
if (loadtimercounter % 5 == 0)
{
loadingpoints = loadingpoints + ".";
tbScannedEANPick.Background = Brushes.OrangeRed;
tbScannedEANPick.Text = "Waiting" + loadingpoints;
tbScannedEANPick.IsReadOnly = true;
bitorder = Main.PIOGetValue(jbmis.IO_GET_INPUT, "BoxManInStarted");
}
counter ++;
}
// After 25 seconds stop timer and continue
loadtimer.Stop();
void timer_Tick(object sender, EventArgs e)
{
loadtimercounter += 5;
}
I am searching for half a day... I tried to use Thread.sleep, timer, stopwatch, ... all in main thread or side thread..
Thanks in advance!!
You need to do the work on a separate thread. Look into asynchronous programming
Or you could just simply use multi threading. I would recommend using asynchronous programming for both doing the background work and updating the GUI's textbox control.
You should use a background worker.
There is a dedicated report progress event that can be used to update the loading box that you need.
Background Worker Class and example
I'm in my second quarter of c # programming and i'm working on a POS application. I have my windows form created and I have my basic code done for the first week it was assigned. Now I have to "idiot-proof" my code by making sure that only correct data can be entered. Here's what I have so far:
private void btnAddItem_Click(object sender, EventArgs e)
{
//Declare variables
double dblSalesTax = 0, dblPrice, dblTax, dblSalesPrice;
string strItem, strTaxAdded;
int intQuantity;
bool diffTest = false;
//Process user input
while (!diffTest)
{
diffTest = double.TryParse(txtSalesTax.Text, out dblSalesTax);
}
while (dblSalesTax < 0 || dblSalesTax > 25)
{
MessageBox.Show("Please enter a valid tax.");
txtSalesTax.Clear();
diffTest = false;
}
intQuantity = Convert.ToInt16(txtQuantity.Text);
dblPrice = Convert.ToDouble(txtPrice.Text);
dblSalesPrice = dblPrice * intQuantity;
strItem = cbxItem.Text;
intQuantity = Convert.ToInt16(txtQuantity.Text);
dblSubtotal += dblSalesPrice;
if (chkTaxExempt.Checked)
{
dblTax = 0;
strTaxAdded = "";
}
else
{
dblTax = dblSalesPrice * dblSalesTax;
strTaxAdded = "*";
}
dblTaxTotal += dblTax;
lbxTally.Items.Add(strItem + ", " + dblSalesPrice.ToString("C") + strTaxAdded);
//Reset Form
txtPrice.Clear();
txtQuantity.Clear();
chkTaxExempt.Checked = false;
cbxItem.Focus();
}
private void btnEndSale_Click(object sender, EventArgs e)
{
dblGrandTotal = dblSubtotal + dblTaxTotal;
lbxTally.Items.Add("");
lbxTally.Items.Add("");
lbxTally.Items.Add("Subtotal: " + dblSubtotal.ToString("C"));
lbxTally.Items.Add("Tax Total: " + dblTaxTotal.ToString("C"));
lbxTally.Items.Add("Grand Total: " + dblGrandTotal.ToString("C"));
}
private void btnPay_Click(object sender, EventArgs e)
{
double dblPay, dblChange;
dblPay = Convert.ToDouble(txtPay.Text);
dblChange = dblPay - dblGrandTotal;
lbxTally.Items.Add("");
lbxTally.Items.Add("Amount Paid: " + dblPay.ToString("C"));
lbxTally.Items.Add("Change: " + dblChange.ToString("C"));
}
Variables being declared beforehand and diffTest being initialized as false.
The assignment is to make sure that the sales tax entered is between 0 and 25 and that they can't enter words or anything else. I thought I did it right but when I run it, I have an infinite loop on my message box and I can't figure out how to get out of it correctly (entering break just gets me out but keeps the input). I have google'd to my hearts content but haven't found a solution but I feel like it's because my code is reusing what's in the text box automatically (I could be very wrong!). Once I get this i'll have to "idiot-proof" my other inputs but I haven't tried yet cause I'm still stuck on this first one. I'm a beginner programming student so any help is appreciated.
You don't need a while statement there. Change it to:
if (dblSalesTax < 0 || dblSalesTax > 25)
Because you have there a while loop as soon as you step into that loop you will never get out because your condition will be always true.
My guess is that you should change While to If.
private void btnAddItem_Click(object sender, EventArgs e)
{
//Declare variables
double dblSalesTax = 0, dblPrice, dblTax, dblSalesPrice;
string strItem, strTaxAdded;
int intQuantity;
bool diffTest = false;
//Process user input
//While (!diffTest)
//{
diffTest = double.TryParse(txtSalesTax.Text, out dblSalesTax);
//}
// Check if the value gets parsed and is in range, otherwise show error and
//exit from this handler
If (!diffTest || dblSalesTax < 0 || dblSalesTax > 25) '<--- Change While to If
{
MessageBox.Show("Please enter a valid tax.");
txtSalesTax.Clear();
diffTest = false;
return; // Return from here since validation failed
}
...
...
...
}
May you can try using Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork()); withawait for the the task that has your loop, something like below:
Parallel.Invoke(
() =>
{
Console.WriteLine("Begin first task...");
}, // close first Action
async () =>
{
Console.WriteLine("Begin second task...");
while (true)
{
// HERE you are the code you need to be executed in infinite loop
await Task.Delay(60000);
}
}, //close second Action
() =>
{
Console.WriteLine("Begin third task...");
} //close third Action
); //close parallel.invoke
Console.WriteLine("Returned from Parallel.Invoke");
This is the numeric changed event code with timer2 wich didnt solve hte problem the function im calling is DoThresholdCheck()
The problem is that in this function im creating each time im changing the numeric value a temp list each time im moving the numeric value change it the list is been created from the start. The problem is that if im using a big file in my program the list is containing sometimes 16500 indexs and its taking time to loop over the list so i guess when im changing the numeric value its taking time to loop over the list. If im using smaller video file for example the list containing 4000 indexs so there is no problems. I tried to use Timer2 maybe i could wait 0.5 seconds between each time the numeric value is changed but still dosent work good.
When im changing the numeric value while the program is running on a big video file its taking the values to be changed like 1-2 seconds ! thats a lot of time.
Any way to solve it ? Maybe somehow to read the list loop over the list faster even if the list is big ?
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
Options_DB.Set_numericUpDownValue(numericUpDown1.Value);
if (isNumericChanged == true)
{
isNumericChanged = false;
myTrackPanelss1.trackBar1.Scroll -= new EventHandler(trackBar1_Scroll);
DoThresholdCheck();
counter = 0;
}
}
private void timer2_Tick(object sender, EventArgs e)
{
counter++;
if (counter > 1)
{
isNumericChanged = true;
//timer2.Stop();
}
}
This is the DoThresholdChecks() function code:
private void DoThresholdCheck()
{
List<string> fts;
//const string D6 = "000{0}.bmp";
if (Directory.Exists(subDirectoryName))
{
if (!File.Exists(subDirectoryName + "\\" + averagesListTextFile + ".txt"))
{
return;
}
else
{
bool trackbarTrueFalse = false ;
fts = new List<string>();
int counter = 0;
double thershold = (double)numericUpDown1.Value;
double max_min_threshold = (thershold / 100) * (max - min) + min;
//label13.Text = max_min_threshold.ToString();
_fi = new DirectoryInfo(subDirectoryName).GetFiles("*.bmp");
for (int i = 0; i < myNumbers.Count; i++)
{
if (myNumbers[i] >= max_min_threshold)
{
//f.Add(i);
string t = i.ToString("D6") + ".bmp";
if (File.Exists(subDirectoryName + "\\" + t))
{
counter++;
button1.Enabled = false;
myTrackPanelss1.trackBar1.Enabled = true;
trackbarTrueFalse = true;
label9.Visible = true;
// myTrackPanelss1.trackBar1.Scroll += new EventHandler(trackBar1_Scroll);
//myTrackPanelss1.trackBar1.Minimum = 0;
// myTrackPanelss1.trackBar1.Maximum = f.Count;
// myTrackPanelss1.trackBar1.Value = f.Count;
// myFiles = new Bitmap(myTrackPanelss1.trackBar1.Value);
}
else
{
label9.Visible = false;
trackbarTrueFalse = false;
button1.Enabled = true;
myTrackPanelss1.trackBar1.Enabled = false;
myTrackPanelss1.trackBar1.Value = 0;
pictureBox1.Image = Properties.Resources.Weather_Michmoret;
label5.Visible = true;
secondPass = true;
break;
}
//fts.Add(string.Format(D6, myNumbers[i]));
}
}
//myTrackPanelss1.trackBar1.Maximum = _fi.Length - 1;
if (myTrackPanelss1.trackBar1.Maximum > 0)
{
if (trackbarTrueFalse == false)
{
myTrackPanelss1.trackBar1.Value = 0;
}
else
{
myTrackPanelss1.trackBar1.Maximum = counter;
myTrackPanelss1.trackBar1.Value = 0;
SetPicture(0);
myTrackPanelss1.trackBar1.Scroll += new EventHandler(trackBar1_Scroll);
}
//checkBox2.Enabled = true;
}
if (_fi.Length >= 0)
{
label15.Text = _fi.Length.ToString();
label15.Visible = true;
}
}
}
else
{
button1.Enabled = true;
}
}
try to cache results from DoThresholdCheck method
You can't magically get around the time the processing takes, if the processing is really necessary. You've got a couple of avenues to explore:
1) Minimise the processing being done - is all of it necessary? Can you cache any of it and only recompute a small amount each time the value changes?
2) Do the processing less often - do you have to recompute every single time?
3) Do the processing on another thread - then at least your UI remains responsive, and you can deliver the results to the UI thread when the background task is complete. However, this is going to be a relatively complicated variant of this pattern as you're going to need to be able to abort and restart if the value changes again while you're still processing the previous one.