This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
WinForm Application UI Hangs during Long-Running Operation
(3 answers)
Closed 3 years ago.
My program displays the time in a timer event and a button starts a function that keeps reading the content of a file until it reaches 50 lines.
The test file is created by a different process that once in a while appends some lines to it.
How can I modify the program to avoid blocking the form during
execution ?
The difference compared to WinForm Application UI Hangs during Long-Running Operation is that the functions called have to update some elements of the form.
And I don't want to use Application.DoEvents(),
I have hundreds of lines of Application.DoEvents() in my programs and sometimes they create a mess.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
void Timer1Tick(object sender, EventArgs e)
{
UpdateTime();
}
void UpdateTime()
{
DateTime dt = DateTime.Now;
textBox1.Text = dt.ToString("hh:mm:ss");
}
void BtnRunClick(object sender, EventArgs e)
{
int nlines = 0;
while(nlines < 50) {
nlines = listBox1.Items.Count;
this.Invoke(new Action(() => ReadLinesFromFile()));
Thread.Sleep(1000);
}
}
void ReadLinesFromFile()
{
string sFile = #"D:\Temp1\testfile.txt";
string[] lines = File.ReadAllLines(sFile);
listBox1.Items.Clear();
foreach(string line in lines) {
listBox1.Items.Add(line);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
}
}
Asynchronous approach for IO operation will execute all operations on the same UI thread without blocking it.
private async void BtnRunClick(object sender, EventArgs e)
{
int nlines = 0;
while(nlines < 50) {
nlines = listBox1.Items.Count;
await ReadLinesFromFile();
await Task.Delay(1000);
}
}
private async Task ReadLinesFromFile()
{
var file = #"D:\Temp1\testfile.txt";
string[] lines = await ReadFrom(file);
listBox1.Items.Clear();
foreach(string line in lines) {
listBox1.Items.Add(line);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
}
private async Task<string[]> ReadFrom(string file)
{
using (var reader = File.OpenText(file))
{
var content = await reader.ReadToEndAsync();
return content.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
}
}
You need only to invoke the ui updates:
void BtnRunClick(object sender, EventArgs e)
{
new Thread(Run).Start();
}
void Run()
{
int nlines = 0;
while (nlines < 50)
{
nlines = listBox1.Items.Count;
ReadLinesFromFile();
Thread.Sleep(1000);
}
}
void ReadLinesFromFile()
{
string sFile = #"D:\Temp1\testfile.txt";
string[] lines = File.ReadAllLines(sFile);
listBox1.InvokeOnUIThread(() => {
listBox1.Items.Clear();
foreach (string line in lines)
{
listBox1.Items.Add(line);
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
});
}
Add this class to your project:
public static class ControlExtensions
{
public static void InvokeOnUIThread(this Control control, Action action)
{
if (control.InvokeRequired)
{
control.Invoke(action);
}
else
{
action();
}
}
}
All you just need is Async and Await markers to achieve this.
This you can apply to problems which have a long running operation that continuously updates your UI.
The below snippet continuously updates TextBox.Text in a loop, giving it the appearance of a timer. LongRunningProcess() simulates a time taking action
async Task LongRunningProcess()
{
await Task.Delay(1000);
}
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
DateTime dt = DateTime.Now;
textBox1.Text = dt.ToString("hh:mm:ss");
await Task.Run(() => LongRunningProcess());
}
}
If you want to know more about Asynchrnous programming in C# you can
refer to below article by Stephen Cleary who is THE Authority in this
field
https://blog.stephencleary.com/2012/02/async-and-await.html
Related
I have asynchronous programming counter and print to richtextbox as below.
private void btnCount_Click(object sender, EventArgs e)
{
Task task = uplineCount();
task.ContinueWith(t =>
{
Invoke(new Action(() =>
{
rtCount.AppendText("mmmmmm");
}));
});
Task.WhenAll(task);
}
public async Task uplineCount()
{
for (int i = 0; i < nuMaxValue.Value; i++)
{
rtCount.AppendText(i.ToString() + "\n");
await Task.Delay(1000);
}
}
Why i don't need Invoke when AppendText in uplineCount method?
Is it bad practice to write code like this. What I want to accomplish is that a user can press a button on a control. The button starts some kind of analyzing process and for each item done it shows a result to the user.
private IEnumerable<int> AnalyzeItems() {
for(int i = 0; i < 1000; i++) {
Thread.Sleep(500);
yield return i;
}
}
private void PerformTask_Click(object sender, EventArgs e) {
Task.Run(() => {
foreach (var item in AnalyzeItems()) {
ResultLog.Invoke((Action)delegate() { ResultLog.Text += item.ToString(); });
}
});
}
why do not use Backgroundworker?
First setup the backgroundworker properties to:
WorkerReportsProgress = true
WorkerSupportsCancellation = true
This is the code:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < 1000; i++) {
Thread.Sleep(500);
if (backgroundWorker1.CancellationPending) {
e.Cancel = true;
break;
}
backgroundWorker1.ReportProgress(i / 10, "step " + i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
label1.Text = e.UserState.ToString();
progressBar1.Value = e.ProgressPercentage;
}
private void button1_Click(object sender, EventArgs e) {
cancelButton.Focus();
button1.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
private void cancelButton_Click(object sender, EventArgs e) {
backgroundWorker1.CancelAsync();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
button1.Enabled = true;
if (e.Error != null) {
MessageBox.Show(e.Error.Message, "Unexpected error");
}
if (e.Cancelled) {
MessageBox.Show("Process stopped by the user", "Cancelled");
}
label1.Text = "Press start";
progressBar1.Value = progressBar1.Minimum;
}
}
Is your approach bad practice? It depends.
If you don't expect your code inside Task.Run to throw any exceptions and you want to continue doing something else, then your code is ok. However, if you want to capture any possible exceptions and wait for the process to finish without freezing UI, then you might want to consider using async/await.
private async void PerformTask_Click(object sender, EventArgs e) {
try
{
await Task.Run(() => {
foreach (var item in AnalyzeItems()) {
ResultLog.Invoke((Action)delegate() { ResultLog.Text += item.ToString(); });
}
});
}
catch(Exception ex)
{
// handle...
}
}
Alternative approach would be to use IProgress<T>. This allows for easy separation of long running work and updating UI. Please note that you shouldn't call this method too often, because
This will put too much work on UI thread resulting in UI freeze.
If you pass any valuetype to IProgress<T>.Report method, then it gets copied. If you call this too often, you risk running garbage collector very often resulting in even bigger freezes.
All of this means that you should utilize IProgress only for truly long running work.
Now that we have it all out of the way, here is a sample of how you could notify users about progress of analyzed items:
private double _currentProgress;
public double CurrentProgress {
get => _currentProgress;
set
{
_currentProgress = value;
NotifyPropertyChanged();
}
}
private async void PerformTask_Click(object sender, EventArgs e)
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, p) => CurrentProgress = p;
await Task.Run(() => AnalyzeItems(Enumerable.Range(0, 5000).ToList(), progress));
}
private void AnalyzeItems(List<int> items, IProgress<double> progress)
{
for (int itemIndex = 0; itemIndex < items.Count; itemIndex++)
{
// Very long running CPU work.
// ...
progress.Report((double)itemIndex * 100 / items.Count);
}
}
If AnalyzeItems takes less than 100 ms for individual item, then you don't want to report after every finished item (see why above). You can decide how often you want to update status like this:
private void AnalyzeItems(List<int> items, IProgress<double> progress)
{
var lastReport = DateTime.UtcNow;
for (int itemIndex = 0; itemIndex < items.Count; itemIndex++)
{
// Very long running work.
Thread.Sleep(10);
// Tell the user what the current status is every 500 milliseconds.
if (DateTime.UtcNow - lastReport > TimeSpan.FromMilliseconds(500))
{
progress.Report((double)itemIndex * 100 / items.Count);
lastReport = DateTime.UtcNow;
}
}
}
If you have really a lot of very fast iterations, you may want to consider changing DateTime.Now to something else.
I am trying to sort an array of 5 random integers in a listbox to ascending order. While creating a new thread and calling it separately, the form becomes unresponsive when I select the 'sort' button.
public partial class Form1 : Form
{
const int iSize = 5;
int[] iArray = new int[iSize];
Random rnd = new Random();
public Form1()
{
InitializeComponent();
}
Thread th;
private void btnGenerate_Click(object sender, EventArgs e)
{
for (int i = 0; i < iArray.Length; i++)
{
iArray[i] = rnd.Next(0, 5);
lbxNumbers.Items.Add(iArray[i]);
}
}
public void threadMethod()
{
bubbleSort(iArray);
foreach(int item in iArray)
{
lbxNumbers.Items.Add(item);
}
}
private void btnSort_Click(object sender, EventArgs e)
{
th = new Thread(threadMethod);
lblStatusUpdate.Text = "Sorting...";
}
private void btnClear_Click(object sender, EventArgs e)
{
lbxNumbers.Items.Clear();
}
public static void bubbleSort(int [] iArray)
{
bool isSorted = false;
int lastUnsorted = iArray.Length - 1;
while(!isSorted)
{
for(int i = 0; i < iArray.Length-1; i++)
{
if(iArray[i] > iArray[i+1])
{
swap(iArray, i, i + 1);
isSorted = false;
}
}
}
}
public static void swap(int [] iArray, int i, int j)
{
int tmp = iArray[i];
iArray[i] = iArray[j];
iArray[j] = tmp;
}
}
I am uncertain where the thread actually kicks in. The listbox can generate the array immediately and clear it, but sort makes it freeze. I am also unsure whether I need to use the background worker tool on this form.
To expound on Dour High Arch's comment.
EDIT
There are a couple of issues with your code. UI updates can only be done on the UI thread. You can accomplish this with the SychronizationContext or async/await handles it for you. Also, your bubblesort was missing an exit condition for the while loop, so it would run forever. The following demonstrates how this would work using async/await.
private async void btnSort_Click(object sender, EventArgs e)
{
lblStatusUpdate.Text = #"Sorting...";
await Run();
}
public async Task Run()
{
//This line runs asynchronously and keeps the UI responsive.
await bubbleSort(iArray);
//The remaining code runs on the UI thread
foreach (int item in iArray)
{
lbxNumbers.Items.Add(item);
}
}
public async Task bubbleSort(int[] iArray)
{
await Task.Run(() =>
{
bool isSorted = false;
while (!isSorted)
{
isSorted = true; //You were missing this to break out of the loop.
for (int i = 0; i < iArray.Length - 1; i++)
{
if (iArray[i] > iArray[i + 1])
{
swap(iArray, i, i + 1);
isSorted = false;
}
}
}
});
}
You can not act on UI, or Main thread within of another.
This line :
lbxNumbers.Items.Add(item);
which is invoked from another thread can cause a trouble.
The proper way of handling this scenario in WindowsForms would be using of
Control.Invoke method.
I created a small class
public class MyProgress
{
public string Report1 { get; set; }
public string Report2 { get; set; }
}
Then in a method i want to report in real time each filename:
private void DirSearch(string root, string filesExtension,string textToSearch)
{
List<MyProgress> prog = new List<MyProgress>();
string[] filePaths = Directory.GetFiles(root, filesExtension, SearchOption.AllDirectories);
for (int i = 0; i < filePaths.Length; i++)
{
int var = File.ReadAllText(filePaths[i]).Contains(textToSearch) ? 1 : 0;
if (var == 1)
{
string filename = filePaths[i];
prog.Add(new MyProgress { Report1 = filename });
}
}
backgroundWorker1.ReportProgress(0, prog);
}
Not sure if the ReportProgress should be out the FOR loop or inside after the prog.Add line.
And then in the progresschanged event
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
foreach (p in (e.UserState as List<MyProgress>))
{
Console.WriteLine("Progress for {0} is {1}", p.);
}
}
I'm getting errors in the foreach i'm not sure what type should be the variable 'p'
I tried:
foreach (List<MyProgress> p in (e.UserState as List<MyProgress>))
But getting error:
Error 8 Cannot convert type 'Search_Text_In_Files.Form1.MyProgress' to 'System.Collections.Generic.List'
OP totally changed the question
you don't know what p should be?
hint you are enumerating a List of MyProgress
foreach (var p in (e.UserState as List<MyProgress>))
but you are passing a string
backgroundWorker1.ReportProgress(0, filename);
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine(e.UserState.ToString());
}
and this is the typical signature of a do work
even if DirSearch is the working method you should be passing BackgroundWorker to it IMHO
and you should pass your results out as .Result - prog
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
// Assign the result of the computation
// to the Result property of the DoWorkEventArgs
// object. This is will be available to the
// RunWorkerCompleted eventhandler.
e.Result = ComputeFibonacci((int)e.Argument, worker, e);
}
Not sure if the ReportProgress should be out the FOR loop or inside after the prog
You want to report progress from inside your for loop that is doing the work. If you pass a list of progress reports in a single call to ReportProgress, backgroundWorker1_ProgressChanged will be invoked just once at the end of your processing. All of your progress reports could come at once, when all the work is done.
Your current code reports progress as it is being made, and attempts to report it again after everything is done. You pass different data types into the same progress report handler, string and List<MyProgress>.
for (int i = 0; i < filePaths.Length; i++)
{
int var = File.ReadAllText(filePaths[i]).Contains(textToSearch) ? 1 : 0;
if (var == 1)
{
string filename = filePaths[i];
backgroundWorker1.ReportProgress(0, filename);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine("Progress for {0} is {1}", e.UserState);
}
I am designing a program that depends on monitoring the battery level of the computer.
This is the C# code I am using:
PowerStatus pw = SystemInformation.PowerStatus;
if (pw.BatteryLifeRemaining >= 75)
{
//Do stuff here
}
My failed attempt of the while statement, it uses all the CPU which is undesirable.
int i = 1;
while (i == 1)
{
if (pw.BatteryLifeRemaining >= 75)
{
//Do stuff here
}
}
How do I monitor this constantly with an infinite loop so that when it reaches 75% it will execute some code.
Try Timer:
public class Monitoring
{
System.Windows.Forms.Timer timer1 = new System.Windows.Forms.Timer();
public Monitoring()
{
timer1.Interval = 1000; //Period of Tick
timer1.Tick += timer1_Tick;
}
private void timer1_Tick(object sender, EventArgs e)
{
CheckBatteryStatus();
}
private void CheckBatteryStatus()
{
PowerStatus pw = SystemInformation.PowerStatus;
if (pw.BatteryLifeRemaining >= 75)
{
//Do stuff here
}
}
}
UPDATE:
There is another way to do your task complete. You can use SystemEvents.PowerModeChanged.
Call it and wait for changes, monitor the changes occured then do your stuff.
static void SystemEvents_PowerModeChanged(object sender, Microsoft.Win32.PowerModeChangedEventArgs e)
{
if (e.Mode == Microsoft.Win32.PowerModes.StatusChange)
{
if (pw.BatteryLifeRemaining >= 75)
{
//Do stuff here
}
}
}
While loop will cause your UI to response poor and the application will get crashed. You can solve this by using many ways. Please check out the below code snippet will help your needs.
public delegate void DoAsync();
private void button1_Click(object sender, EventArgs e)
{
DoAsync async = new DoAsync(GetBatteryDetails);
async.BeginInvoke(null, null);
}
public void GetBatteryDetails()
{
int i = 0;
PowerStatus ps = SystemInformation.PowerStatus;
while (true)
{
if (this.InvokeRequired)
this.Invoke(new Action(() => this.Text = ps.BatteryLifePercent.ToString() + i.ToString()));
else
this.Text = ps.BatteryLifePercent.ToString() + i.ToString();
i++;
}
}
BatteryChargeStatus.Text = SystemInformation.PowerStatus.BatteryChargeStatus.ToString();
BatteryFullLifetime.Text = SystemInformation.PowerStatus.BatteryFullLifetime.ToString();
BatteryLifePercent.Text = SystemInformation.PowerStatus.BatteryLifePercent.ToString();
BatteryLifeRemaining.Text = SystemInformation.PowerStatus.BatteryLifeRemaining.ToString();
PowerLineStatus.Text = SystemInformation.PowerStatus.PowerLineStatus.ToString();
If you want to perform some operation just convert these string values into the integer.