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?
Related
I have this Thread that counts down from 5 to 0:
public static int seconds = 5;
public static void startTimer()
{
Thread thread = new Thread(() =>
{
while (seconds > 0)
{
Thread.Sleep(1000);
seconds--;
}
});
thread.IsBackground = true;
thread.Start();
}
I want to Update the text value of a label "label1" to the seconds remaining on my main form "Form1",
I currently am using this inside the thread:
Application.OpenForms["Form1"].Controls["label1"].Invoke(new Action(() => Application.OpenForms["Form1"].Controls["label1"].Text = str));
what would be the correct way of doing this,
I need to create a custom thread for an assignment.
Is this way correct and if not how do I reference the text property of label1 from the thread.
You can capture correct dispatcher and pass it to thread like this:
private void button1_Click(object sender, EventArgs e)
{
int seconds = 5;
var dispatcher = Dispatcher.CurrentDispatcher;
Task.Run(async () =>
{
while(seconds > 0)
{
await Task.Delay(1000);
seconds--;
dispatcher.Invoke(() => textBox1.Text = seconds.ToString());
}
});
}
With no dispatcher can be done like this:
private void button1_Click(object sender, EventArgs e)
{
int seconds = 5;
ISynchronizeInvoke invoker = this;
Task.Run(async () =>
{
while(seconds > 0)
{
await Task.Delay(1000);
seconds--;
Action updateMethod = () => textBox1.Text = seconds.ToString();
invoker.Invoke(updateMethod, new object[] { });
}
});
}
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
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.
The usual routine, progress bar, user clicks the button, process runs, progress bar is updated. Classic.
However I would like to run the process the moment entire app starts, so I run it in Loaded handler for Window (the only window in my app).
And now, the progress bar is not updated. How to overcome this problem?
Tester for updating progress bar.
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Render, new Action(delegate() { ++Progress.Value; }));
}
Because you put all the work on the UI thread, which is used to update the Progressbar, so the Progressbar is not updated until the work is done. Put the work on a background thread, and update the Progressbar from the background thread using Dispatcher.BeginInvoke.
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(() =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
If you are using .NET 2.0 (not using TPL), you can use ThreadPool,
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem( (o) =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
Or using await-async (.NET 4.5), see, you can call DoWork() from any method that marked as async.
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await DoWork();
}
async Task DoWork()
{
await Task.Run(async () =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
await Progress.Dispatcher.BeginInvoke(new Action(delegate() { ++Progress.Value; }), null);
}
});
}
I am using Task class in my app. This is NOT WPF application! The question is are there any possibilities of calling function from Task body on UI thread, like this:
var task = new Task(() => DoSmth(1));
task.Start();
public void DoSmth(int arg)
{
//smth
CallNotifFuncOnUIThread(() => Notify(1));
//smth ELSE
CallNotifFuncOnUIThread(() => Notify(2));//notify AGAIN
//smth ELSE
}
public void Notify(int arg)
{
progressBar1.Value = arg;
}
Or maybe there are other solutions of this problem? I know about BackgroundWorker class, but what about Tasks?
You can always call other methods inside your DoSth()
Dispatcher.Invoke(...);
Dispatcher.BeginInvoke(...);
You can also user Task.ContinueWith(...) to do sth after the task is finished processing ...
If you have a task you can start it on the gui thread by providing the correct scheduler:
Task.Factory.StartNew(() => DoSomethingOnGUI(), TaskScheduler.FromCurrentSynchronizationContext());
UI is usually STA see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms680112(v=vs.85).aspx
so in order to do something from none UI thread in the UI you need to inject somehow the msg into the thread
see for example htis winform example:
http://weblogs.asp.net/justin_rogers/articles/126345.aspx
watever UI you are using you will need a similar system.
With windows Form and progressBar1 component on ityou can use TPL IProgress interface for Task.
private void Form1_Load(object sender, EventArgs e)
{
Progress<int> progress = new Progress<int>();
var task = Alg(progress);
progress.ProgressChanged += (s, i) => { UpdateProgress(i); };
task.Start();
}
public void Notify(int arg)
{
progressBar1.Value = arg;
}
public static Task Alg(IProgress<int> progress)
{
Task t = new Task
(
() =>
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
((IProgress<int>)progress).Report(i);
}
}
);
return t;
}