Background operation and blocking main form - c#

Here is my scenario:
On a form I have list of direcotories, Button and control to display multiline text.
In a loop I try to find all files in each directory and delete them.
When file is deleted i want to add text to multiline control.
My problem is that when text is added I can not do anything else. Form is blocked and if I try do do anytching it just stops responding.
Files are deleted using BackgroundWorker
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//this is datatable with directories and other info
MainDataset.CZYSZCZENIEDataTable CZYSZCZENIE = e.Argument as MainDataset.CZYSZCZENIEDataTable;
CzyscPliki(CZYSZCZENIE, ReportProgress);
}
private void CzyscPliki(MainDataset.CZYSZCZENIEDataTable CZYSZCZENIE, ReportProgressDel del)
{
DirectoryInfo dir = null;
FileInfo[] files = null;
bool subfolder = false;
string katalog = "";
string maska = "";
string[] maski = null;
long total=0;
string dirS;
string fileS;
long fileLen;
//foreach directory to delete
foreach (DataRow r in CZYSZCZENIE.Rows)
{
//CanRead - check if row is not deleted or detached
//r["CZYSC"].AsBool() - check if directory should be cleared
if (r.CanRead() && r["CZYSC"].AsBool())
{
subfolder = r["PODKATALOGI"].AsBool();
katalog = r["KATALOG"].AsString().TrimEnd('\\');
maska = r["MASKA"].AsString();
if (maska.IsEmpty())
maska = "*";
maski = maska.Split(';');
dir = new DirectoryInfo(katalog);
if (dir.Exists)
{
foreach (string s in maski)
{
files = dir.GetFiles(s, (subfolder ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly));
dir.GetFiles();
foreach (FileInfo f in files)
{
dirS = f.Directory.FullName;
fileS = f.Name;
fileLen = f.Length;
try
{
f.Delete();
total += fileLen;
if (del != null)
//here is problem: del - delegate to report state
//when it is called it blocks form
del(dirS, fileS, fileLen, total);
}
catch (Exception ex)
{ }
}
}
}
}
}
}
//this is the delegate that appends text in multiline control
//memoEdit1 is the control
//ceReportProgress.Checked - check if report should be added
private void ReportProgress(string directory, string file, long size, long totalSize)
{
if (memoEdit1.InvokeRequired)
{
memoEdit1.BeginInvoke(new Action<string, string, long, long>(ReportProgress), directory, file, size, totalSize);
}
else
{
if (ceReportProgress.Checked)
{
if (file.IsEmpty())
memoEdit1.AppendText("\r\nCzyszczenie katalogu " + directory);
else
{
memoEdit1.AppendText(file);
if (size > 0)
{
if (size > 1048576)
{
decimal d = size / 1048576;
d = decimal.Round(d, 2);
memoEdit1.AppendText("\tWielkość : " + d.AsString() + " megabajtów", false);
}
else if (size > 1024)
{
decimal d = (decimal)size / (decimal)1024;
d = decimal.Round(d, 2);
memoEdit1.AppendText("\tWielkość : " + d.AsString() + " kilobajtów", false);
}
else
memoEdit1.AppendText("\tWielkość : " + size.AsString() + " bajtów", false);
}
if (totalSize > 0)
{
if (totalSize > 1073741824)
{
decimal d = (decimal)totalSize / (decimal)1073741824;
d = decimal.Round(d, 2);
memoEdit1.AppendText("Zwolniono dotychczas : " + d.AsString() + " gigabajtów");
}
else if (totalSize > 1048576)
{
decimal d = (decimal)totalSize / (decimal)1048576;
d = decimal.Round(d, 2);
memoEdit1.AppendText("Zwolniono dotychczas : " + d.AsString() + " megabajtów");
}
else if (totalSize > 1024)
{
decimal d = (decimal)totalSize / (decimal)1024;
d = decimal.Round(d, 2);
memoEdit1.AppendText("Zwolniono dotychczas : " + d.AsString() + " kilobajtów");
}
else
memoEdit1.AppendText("Zwolniono dotychczas : " + totalSize.AsString() + " bajtów");
}
}
//scroll to the end of control
memoEdit1.ScrollToEnd();
}
}
}
How can I improve this to make it not blocking the form?

You are calling ReportProgress too often. Do it more than about 1000 times per second and the UI thread gets flooded with requests that it cannot keep up with. It won't get around to doing its normal duties, which include painting the controls and responding to the mouse and keyboard. It looks frozen. This gets worse when the UI update code gets more expensive, updating text in a TextBox when there's already a lot of text in it can get quite slow.
The diagnostic is still seeing the UI frozen for a while after the BGW stops running, working on emptying the backlog in the invoke request queue, then suddenly jumping back alive when the queue is finally emptied.
You need to throttle the rate at which you call BeginInvoke(). It never makes more sense to call it any more frequently than once every 50 milliseconds, a human cannot perceive the difference beyond that. Collect the info in a List<> so you can BeginInvoke() a lot less frequently. That's still no complete guarantee if your worker can produce results faster than the UI thread could ever keep up with. In which case slowing down the worker would be a fix. Easy by using Invoke instead of BeginInvoke.

If this worker is running asynchronously, then you can have a form which responds to you.
Besides, problems:
You are running the loop in another function - it makes the operation non-reponsive.
You are not even checking if user wants to cancel (just a point i wanted to make) - Handle DoWorkEventArgs's Cancel property inside the foreach loop.
Move the function CzyscPliki's code in the backgroundWorker1_DoWork (it's anyway too tiny).
EDIT:
If you don't want to move the code into DoWork event handler, then better use Thread for more control. I'm not an expert on it but you will find plenty of code on how to implement so.

Related

C# Why is Serial.WriteLine() so slow?

When I want to program a Teensy 3.5 micro-controller, I send it a .HEX file via a hardware serial port. I have two ways of doing this; one way is to use a serial comms app like Tera Term to send the file, and another way is via a small C# command-line app I wrote to do the same thing.
When I send a 3000+ line Hex file to the micro-controller, it takes approximately 14 seconds. When I do the same thing with my C# program, it takes twice as long (or longer!). The baud rate for both Tera Term and my C# program are the same, and of course the Teensy setup is common to both.
When I looked as the serial transfer using a digital scope, I see the following (same time scale in both photos):
The first photo shows the transfer when using Tera Term, and the second one shows the transfer when using my C# program using Serial.WriteLine() to transfer file contents line-by-line, as shown below:
using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Timers;
using System.Diagnostics;
/*
Small console app to facilitate over-the-air (OTA) updates to a Teensy 3.x/4.x controller,
using VS2019 with the Visual Micro extension as the Arduino IDE. It is called by a post-build
'hook' statement in a file called 'board.txt' located in whatever Teensy program is
being updated. This app does the following:
- Extract the project path and selected COMPORT number from the arguments to the call to Main()
- Opens a UART serial port connection to the Teensy, typically one provided by a BT adaptor
operating in 'pass-through' mode. The serial port COMPORT number is passed into this app
as an argument.
- Sends whatever command is required to put the existing Teensy firmware into 'update' mode
- Using the path of the updating program (passed in as an argument), locates the .HEX file
associated with the project, and sends it's contents to the Teensy, one line at a time, counting
lines and confirming checksums line-by-line
- Compares the number of lines sent to the Teensy with the number of lines received by the Teensy,
and if there is a match, allows the Teensy update process to complete; otherwise aborts
*/
namespace TeensyFlash
{
class Program
{
const string startCmdStr = "U"; //used in sketch's 'GetUserInput()' to start upload sequence
static string rcvStr = string.Empty;
private static System.Timers.Timer aTimer;
private static bool bTimedOut;
static void Main(string[] args)
{
//Extract the build path and selected COMPORT number from the arguments to the call to Main()
Console.WriteLine("Teensy Flash Console");
Console.WriteLine("Number of arguments in args = {0}\n", args.Length);
int argindex = 0;
string comPortStr = string.Empty;
foreach (var item in args)
{
Console.WriteLine(item);
if (item.Contains("COM"))
{
comPortStr = args[argindex];
}
argindex++;
}
string build_path = args[0];
string projectName = args[args.Length - 1];
projectName = projectName.Substring(0, projectName.Length - 4); //remove extension
build_path = build_path.Replace("\"", string.Empty).Trim();
string hexfilename = build_path + "\\" + projectName + ".hex";
Console.WriteLine("path = {0}", build_path);
Console.WriteLine("comport = {0}", comPortStr);
Console.WriteLine("build name = {0}", projectName);
Console.WriteLine("path to HEX file = {0}", hexfilename);
try
{
Stopwatch stopwatch = Stopwatch.StartNew();
string[] lines = System.IO.File.ReadAllLines(hexfilename);
foreach (string item in lines)
{
Console.WriteLine(item);
}
Console.WriteLine("this took " + stopwatch.ElapsedMilliseconds + " Msec");
}
catch (Exception)
{
throw;
}
Console.WriteLine("Opening Serial Port...");
try
{
SerialPort _serport = new SerialPort(comPortStr, 115200);
_serport.WriteTimeout = 1000;
_serport.WriteBufferSize = 20480;
_serport.Open();
_serport.DiscardOutBuffer();
_serport.DiscardInBuffer();
Thread.Sleep(100);
Console.WriteLine("Sending Trigger Character " + startCmdStr);
Console.WriteLine(startCmdStr);
_serport.Write(startCmdStr);
Console.WriteLine("Waiting for 'waiting' from Teensy...");
rcvStr = string.Empty;
aTimer = new System.Timers.Timer();
//aTimer.Interval = 5000;
aTimer.Interval = 25000;
aTimer.Elapsed += OnTimedEvent;
aTimer.Start();
while (!rcvStr.Contains("waiting") && !bTimedOut)
{
if (_serport.BytesToRead > 0)
{
rcvStr = _serport.ReadLine();
}
}
aTimer.Stop();
if (bTimedOut)
{
Console.WriteLine("Timed out waiting for 'waiting' response from Teensy");
}
else
{
//if we get to here, the Teensy is ready to receive HEX file contents
Console.WriteLine("Received " + rcvStr + " from Teensy");
Stopwatch stopwatch2 = Stopwatch.StartNew();
int numlines = 0;
string[] lines = System.IO.File.ReadAllLines(hexfilename);
foreach (string item in lines)
{
numlines++;
_serport.WriteLine(item);
}
Console.WriteLine("total lines = {0}, time = {1} mSec", numlines, stopwatch2.ElapsedMilliseconds);
//now we wait for Teensy to emit "hex file: xx lines xx bytes..." and then "enter xx to flash..."
aTimer.Start();
while (!rcvStr.Contains("hex file:") && !bTimedOut)
{
if (_serport.BytesToRead > 0)
{
rcvStr = _serport.ReadLine();
}
}
aTimer.Stop();
aTimer.Dispose();
if (bTimedOut)
{
Console.WriteLine("Timed out waiting for 'hex file' response from Teensy");
}
else
{
//extract number of lines from Teensy string, and compare with numlines.
//If they match, then send the number back to Teensy to complete the update.
//Otherwise, send '0' to abort
int colonIdx = rcvStr.IndexOf(':');
int lineIdx = rcvStr.IndexOf("lines");
string compareStr = rcvStr.Substring(colonIdx + 1, lineIdx - colonIdx - 1);
compareStr = compareStr.Trim();
int numTeensyLines = Convert.ToInt16(compareStr);
Console.WriteLine("sent {0} teensy replied {1}", numlines, numTeensyLines);
if (numTeensyLines == numlines)
{
Console.WriteLine("numlines {0} matches numTeensyLines {1} - send confirmation",
numlines, numTeensyLines);
_serport.WriteLine(compareStr);
}
}
}
}
catch (Exception)
{
throw;
}
try
{
}
catch (Exception)
{
throw;
}
}
static string chksum(string input)
{
int TwosComplement(string s)
{
if (s.Length % 2 != 0)
throw new FormatException(nameof(input));
var checksum = 0;
for (var i = 0; i < s.Length; i += 2)
{
var value = int.Parse(s.Substring(i, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
checksum = (checksum + value) & 0xFF;
}
return 256 - checksum & 0xFF;
}
//return string.Concat(":", input, " ", TwosComplement(input).ToString("X2"));
return TwosComplement(input).ToString("X2");
}
private static void SetTimer()
{
// Create a timer with a two second interval.
aTimer = new System.Timers.Timer(2000);
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += OnTimedEvent;
//aTimer.AutoReset = true;
aTimer.Enabled = true;
}
private static void OnTimedEvent(Object source, ElapsedEventArgs e)
{
Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
e.SignalTime);
bTimedOut = true;
}
}
}
The code that actually does the file transfer is:
Stopwatch stopwatch2 = Stopwatch.StartNew();
int numlines = 0;
string[] lines = System.IO.File.ReadAllLines(hexfilename);
foreach (string item in lines)
{
numlines++;
_serport.WriteLine(item);
}
Console.WriteLine("total lines = {0}, time = {1} mSec", numlines, stopwatch2.ElapsedMilliseconds);
When the '_serport.WriteLine(item);' line is commented out, the reported elapsed time is 0 mSec, as expected - so the 'ReadAllLines()' step isn't the problem.
Anyone have an idea why the 'WriteLine(item)' processing is so slow?
It depends on the structure of the method itself with the device, but I found on the same site an inquiry and means for it that help you
stackoverflow: console writeline slow

How to make run real time and faster refresh method with timer

I have script for refresh network with object label and panel but in script using looping mode with 'for'. I want to this real time refresh for 1 sec or 5 sec but because using 'for' make this procces need more time and get stuck screen. how to make the solution more quickly and in real time?
Thanks
public PosPing()
{
InitializeComponent();
RefreshPOS.Tick += new EventHandler(CheckPOSUG);
RefreshPOS.Start();
}
private void CheckPOSUG(object sender, EventArgs e)
{
Panel[] panelUG = new Panel[]{pnlPOSUG1,pnlPOSUG2,pnlPOSUG3,pnlPOSUG4,pnlPOSUG5,pnlPOSUG6,pnlPOSUG7,pnlPOSUG8};
Label[] LabelUG = new Label[]{lblUG1,lblUG2,lblUG3,lblUG4,lblUG5,lblUG6,lblUG7,lblUG8};
Label[] lblSpdUG = new Label[] { lblSpdUG1, lblSpdUG2, lblSpdUG3, lblSpdUG4, lblSpdUG5, lblSpdUG6, lblSpdUG7, lblSpdUG8 };
for (int x = 0; x < 8;x++ )
{
string IP = "192.168.135.1" + (x + 1).ToString();
var ping = new Ping();
var reply = ping.Send(IP, 10 * 1000);
LabelUG[x].Text = "POSBMS10" + x.ToString();
if (reply.Status == IPStatus.Success)
{
lblSpdUG[x].Text = reply.RoundtripTime.ToString() + " " + "ms";
panelUG[x].BackColor = Color.FromName("Lime");
}
else
{
lblSpdUG[x].Text = "Nonaktif";
panelUG[x].BackColor = Color.FromName("ButtonHighlight");
}
}
}
Without a good, minimal, complete code example, it's hard to know for sure how to best answer your question. But it looks like you are trying to ping eight different servers, which are represented by eight set of controls in your form.
If that is correct, then I agree with commenter Hans Passant that you should be using the SendPingAsync() method instead. This will allow you to execute the pings asynchronously, without blocking the UI thread, so that your program can remain responsive.
Because you are dealing with eight different servers, it makes sense to me that you should execute the eight pings asynchronously. To accomplish this, I would refactor the code a bit, putting the server-specific loop body into a separate method, so that each instance can be run concurrently.
Implementing it that way would look something like this:
private async void CheckPOSUG(object sender, EventArgs e)
{
Panel[] panelUG = new Panel[]{pnlPOSUG1,pnlPOSUG2,pnlPOSUG3,pnlPOSUG4,pnlPOSUG5,pnlPOSUG6,pnlPOSUG7,pnlPOSUG8};
Label[] LabelUG = new Label[]{lblUG1,lblUG2,lblUG3,lblUG4,lblUG5,lblUG6,lblUG7,lblUG8};
Label[] lblSpdUG = new Label[] { lblSpdUG1, lblSpdUG2, lblSpdUG3, lblSpdUG4, lblSpdUG5, lblSpdUG6, lblSpdUG7, lblSpdUG8 };
Task[] tasks = new Task[8];
for (int x = 0; x < 8; x++)
{
tasks[x] = PingServer(x, panelUG[x], LabelUG[x], lblSpdUG[x]);
}
try
{
await Task.WhenAll(tasks);
}
catch (Exception e)
{
// handle as appropriate, e.g. log and exit program,
// report expected, non-fatal exceptions, etc.
}
}
async Task PingServer(int index, Panel panel, Label ugLabel, Label spdLabel)
{
// NOTE: String concatenation will automatically convert
// non-string operands by calling calling ToString()
string IP = "192.168.135.1" + (index + 1);
var ping = new Ping();
var reply = await ping.SendPingAsync(IP, 10 * 1000);
ugLabel.Text = "POSBMS10" + x;
if (reply.Status == IPStatus.Success)
{
spdLabel.Text = reply.RoundtripTime + " ms";
// The Color struct already has named properties for known colors,
// so no need to pass a string to look Lime up.
panel.BackColor = Color.Lime;
}
else
{
spdLabel.Text = "Nonaktif";
panel.BackColor = Color.FromName("ButtonHighlight");
}
}

System stops responding while read a large text file using stramreader & pass them in to a data grid

System stops responding while read a large text file using stramreader & pass them in to a data grid.
Appreciates your support to resolve this issue.
here is my code:
private void button2_Click(object sender, EventArgs e)
{
button2.Enabled = false;
button3.Enabled = false;
int lno = 0;
try
{
char[]seps = {',','\n','\r','','','┐',' '};
StreamReader read = new StreamReader(fd.FileName);
line = read.ReadToEnd();
String[]spLine = line.Split(seps);
for (int i = 0; i < spLine.Length; i++)
{
if ((prv.Equals("1:")) || (prv.Equals("1-")))
{
lno++;
display.AppendText(" 1 : " + spLine[i]);
display.AppendText(Environment.NewLine);
dataGridView1.Rows.Add(lno, "1", spLine[i]);
}
else if (prv.Equals("1"))
{
lno++;
display.AppendText(" 1 : " + spLine[i + 1]);
display.AppendText(Environment.NewLine);
dataGridView1.Rows.Add(lno, "1", spLine[i + 1]);
}
else if (prv.Equals("2"))
{
lno++;
display.AppendText(" 2 : " + spLine[i + 1]);
display.AppendText(Environment.NewLine);
dataGridView1.Rows.Add(lno, "2", spLine[i + 1]);
}
prv = spLine[i].Trim();
button2.Enabled = true;
}
}
catch (Exception ex) {}
button2.Enabled = true;
button3.Enabled = true;
}
If you process lots of data in the UI thread, you are blocking the windows event loop, which prevents it painting, and causes it to report as unresponsive. You should do such processing on a worker thread, and then switch back to the UI thread (either at the end, or in batches - not for every row) to perform the UI updates like adding rows to grids.
Yes, this makes it more complicated. Yes, it is necessary if you don't want it to show as unresponsive.

create one time reference for various sample rates per file

My program reads in multiple files that contain time/value pairs sampled at different rates. I'm trying to use the file with the highest sample rate as the time scale for all sampled rates and output one master file with the unique time values from the highest sample rate file.
Each file contains time/values pairs like:
1,58
1.5,90
2,154
2.5,34
Here is my code so far:
public void ReadAndWrite(string[] fileNames)
{
var stopwatch = Stopwatch.StartNew();
List<StreamReader> readers = fileNames.Select(f => new StreamReader(f)).ToList();
try
{
using (StreamWriter writer = new StreamWriter(tbxOutputFile.Text))
{
string line = null;
// For each measurement in max measurements
for (int measNum = 0; measNum < numOfRows; measNum++)
{
// For each file's reader
for (int i = 0; i < readers.Count; i++)
{
// If line contains something, then add it to line
if ((line = readers[i].ReadLine()) != null)
{
// Process line and then write it to file
line = ProcessLine(line);
writer.Write(line);
}
else
{
writer.Write("");
}
// If it's not the last column, add delimiter
if (i < readers.Count - 1)
writer.Write(",");
}
writer.WriteLine();
// Update labels
int val = ((measNum + 1) * 100) / numOfRows;
string newText = Convert.ToString("Progress: " + val + "% " + " " + "Measurement #: " + (measNum + 1)
+ " out of " + numOfRows); // running on worker thread
this.Invoke((MethodInvoker)delegate
{
// runs on UI thread
lblStatus.Text = newText;
progressBar1.Value = val;
});
}
}
}
catch (Exception)
{
throw;
}
finally
{
foreach (var reader in readers)
{
reader.Close();
}
}
MessageBox.Show("File successfully created! " + '\n' + "Elapsed time: " +
(stopwatch.ElapsedMilliseconds/1000) + " seconds", "Processing Complete");
}
I came up with the pseudo code below (currentTime is the time from each file and uniqueTime is from an array that reads in each time from the highest sampled file):
// if time value from individual file is same as uniqueTime
if currentTime == uniqueTime
{
valueToWrite = curr_value // write the current value
}
else // currentTime is not same as uniqueTime
{
valueToWrite = prev_value // write the previous value
}
timeToWrite = uniqueTime // always write the uniqueTime
What is the best way to execute this pseudo code to make a unique time reference for all the various sample rates? Sorry if my question is confusing, I can elaborate more if need be.
To be clear about this, you do not want the values at the specific time they occurred, but you want to display one value for each source at each timepoint that the highest sampled source has?
That should be pretty straightforward. In pseudocode:
foreach (ValuePair val in highSampleRateValues) {
var aggregatedTimePointData;
aggregatedTimePointData.Add(val.Time, val.Value);
foreach (ValuePair val2 in lowSampleRateValues) {
var value = DetermineLatestEntryBackwardFrom(val.Time);
aggregatedTimePointData.Add(value);
}
}
This way, the sample rate of the higher density sampled signal serves as a clock, but you will have inaccuracies since the values from the other sources are only close, but not exactly on the timepoint of their recording. If you want those inaccuracies reduced, choose a higher sample rate and do the same thing. You can get as close to the actual timepoints as you want.

C# application slows Down after 20-30 mins?

I have c# application which reads data from serial port. I have put serial read handler in timer with interval 1 second , because data coming every 1 second
on timer I calling
delegate void SetTextCallback(string text);
ReceivedText(serialPort1.ReadExisting());
I also showing received data in richtextbox just to check it getting proper data or not.
But after 15-20 mins application slows down wont even respond.
private void ReceivedText(string text)
{
if (this.rtbReceived.InvokeRequired)
{
SetTextCallback x = new SetTextCallback(ReceivedText);
this.Invoke(x, new object[] { (text) });
}
else
{
this.rtbReceived.Text += text;
serialdata = text;
if (serialdata.Length > 0 &&
serialdata.Length < 42 &&
serialdata.Contains("#") ||
serialdata.StartsWith(" #"))
{
serialdata.Trim();
splitdata = serialdata.Split(' ');
try
{
txtBathTemp.Text = splitdata[3];
txtBaroPressure.Text = splitdata[4];
double stemp = double.Parse(splitdata[5]);
txtSampleTemp.Text = (Math.Round(stemp, 2)).ToString();
}
catch (Exception EX)
{
MessageBox.Show(ex.Message);
}
}
}
}
There is a chance that this.rtbReceived.Text grows up after some time. Even if it doesn't use huge amount of memory, constantly manipulating String is not efficent. Have you considered using StringBuilder instead?

Categories