The question is pretty trivial: I need to update progress on WPF application while time-consuming calculation is processed. In my tries, I've made some googling and finally based on the first code snippet from this solution: How to update UI from another thread running in another class. And here's my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
namespace ThreadTest
{
public class WorkerClass
{
public int currentIteration;
public int maxIterations = 100;
public event EventHandler ProgressUpdate;
public void Process()
{
this.currentIteration = 0;
while (currentIteration < maxIterations)
{
if (ProgressUpdate != null)
ProgressUpdate(this, new EventArgs());
currentIteration++;
Thread.Sleep(100); // some time-consuming activity takes place here
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnStart_Click(object sender, RoutedEventArgs e)
{
WorkerClass wItem = new WorkerClass();
wItem.ProgressUpdate += (s, eArg) => {
Dispatcher.BeginInvoke((Action)delegate() { txtProgress.Text = wItem.currentIteration.ToString(); });
};
Thread thr = new Thread(new ThreadStart(wItem.Process));
thr.Start();
// MessageBox.Show("Job started...");
while (thr.IsAlive == true)
{
Thread.Sleep(50);
}
MessageBox.Show("Job is done!");
}
}
}
The issue is that if I use Dispatcher.Invoke, than the working thread (thr) gets into WaitSleepJoin state after the first cycle pass and does not resume, therefore the entire application freezes. I've googled several suggestions to use Dispatcher.BeginInvoke instead, but in this case the progress is not updated untill the process finishes the work. I guess the issue is related to switching between threads, but cannot get exact point.
This is a classic "Invoke deadlock" scenario. Stack Overflow has a number of existing questions addressing this problem for Winforms, but I could only find one related question in the WPF context (Deadlock when thread uses dispatcher and the main thread is waiting for thread to finish), but that one isn't precisely the same question, or at least doesn't have answers (yet) that would directly address your question, and the question and answers themselves are poorly enough formed that I feel your question warrants a fresh answer. So…
The basic issue is that you have blocked your UI thread waiting on the process to finish. You should never do this. You should never block the UI thread for any reason. It should be obvious that if you wait for any reason in code running in the UI thread, then the UI itself cannot respond to user input or do any sort of screen refresh.
There are lots of possible ways to address this, but the current idiom for dealing with this would be to use async and await, along with Task to run your process. For example:
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
WorkerClass wItem = new WorkerClass();
wItem.ProgressUpdate += (s, eArg) => {
Dispatcher.BeginInvoke((Action)delegate() { txtProgress.Text = wItem.currentIteration.ToString(); });
};
Task task = Task.Run(wItem.Process);
// MessageBox.Show("Job started...");
await task;
MessageBox.Show("Job is done!");
}
Note that while the above declares the async method as void, this is the exception to the rule. That is, normally one should always declare async methods as returning Task or Task<T>. The exception is situations such as this, where the method is an event handler and so is required to match an existing signature where the method must be declared to return void.
I ran your code as it is, and commented out this :
while (thr.IsAlive == true)
{
Thread.Sleep(50);
}
Everything worked as expected.
/Edit after user comment/
To get notified of processing completion, do these changes :
a. public event EventHandler ProgressCompleted; in your WorkerClass.
b.
if (ProgressCompleted != null)
ProgressCompleted(this, new EventArgs());
after your while finishes in Process() method.
c. In your BtnStart_Click before creating thread.
wItem.ProgressCompleted += (s1, eArgs1) =>
{
MessageBox.Show("Job is done!");
};
Related
I think I am missing something here..
I expected the call to CountCharsAsync() to be non blocking. But it doesn't seem that way. When i click on my button nothing happens for the first five seconds.. and the UI gets stuck...
But the second version is fine.
Could someone explain the difference between these two ways...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AsyncAwaitExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
textBox1.Text += "Count is : ";
int count = await CountCharsAsync();
textBox1.Text += count;
textBox1.Text += "\r\n-----------\r\n";
textBox1.Text += "Count is : ";
Task<int> tsk = new Task<int>(CountChars);
tsk.Start();
int count2 = await tsk;
textBox1.Text += count2;
}
private int CountChars()
{
int count = 50;
Thread.Sleep(5000);
return count;
}
private async Task<int> CountCharsAsync()
{
int count = 50;
Thread.Sleep(5000);
return count;
}
}
}
You're calling Thread.Sleep in your async method. That will block whatever thread it's running on... which is still going to be the UI thread. Calling an async method doesn't start a new thread by itself. Other threads can be involved depending on exactly what happens within the method, but until the first await expression, the method will execute synchronously on the same thread as the calling code... and your async method doesn't have any await expressions (which should already be giving you a warning).
You should use:
await Task.Delay(5000);
instead. That's a sort of "asynchronous wait". That way, the UI thread won't be blocked. The task returned by CountCharsAsync will complete about 5 seconds later, at which point the next part of button1_Click will execute.
Compare that with this code:
Task<int> tsk = new Task<int>(CountChars);
tsk.Start();
That will execute CountChars in the default task scheduler - on the thread pool. Blocking that thread doesn't affect the UI at all. (As noted in comments, that's not a good way of starting a task these days. Either call an async method, or use Task.Run.)
While Daisy's answer makes it not block UI, your CountCharsAsync method is "fake" async. If you have Resharper (not sure if this a feature of VS or R#) you will have a warning:
Async method lacks 'await' operators and will run synchronously
This is because async Task does not do all the magic - in your case it does absolutetly nothing. Nowhere you make it run on another thread.
Proper implementation would be:
private int CountChars()
{
int count = 50;
Thread.Sleep(5000);
return count;
}
private Task<int> CountCharsAsync() // notice no async!
{
return Task.Run(CountChars);
}
Daisy's implementation would still block if you replaced Thread.Sleep with for example a for loop to max integer.
My version in fact shifts execution to new thread.
This question already has answers here:
Unresponsive UI when using BeginInvoke
(3 answers)
Closed 7 years ago.
I have written a program that counts and the value is represented in the label's text.
The process is started by clicking on the button.
When I start, UI freezes.
I want to solve it by delegate.
Where is my bug?
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public delegate void MyDelegate();
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void DelegateMethod()
{
for (int i = 0; i < 9999999999999; i++)
{
label1.Text = i.ToString();
}
MessageBox.Show("OK");
}
private void button1_Click(object sender, EventArgs e)
{
BeginInvoke(new MyDelegate(DelegateMethod));
}
}
}
It was because your UI element is getting updated so frequently and it will keep doing until loop terminates, if you add Thread.Sleep() after every iteration you can see the behaviour different:
for (int i = 0; i < 9999999999999; i++)
{
label1.Text = i.ToString();
// for example delay 1 second
Thread.Sleep(1000);
}
A more better approach is to use async and await keywords introduced in c# which will do extra work in background, not on UI thread, currently all the processing is getting done on your UI thread, which is causing UI thread to get blocked.But in your case that will not make difference, because here the problem is updating UI very fast which causes it to freeze.
It blocks because you are running it on the same thread as the UI. Also, that many updates to a label will not exactly perform very well - even if being updated from another thread.
If you really want to do that, create a new thread/task and invoke the UI update properly from there.
Because you do it on the UI-Thread. Try Task.Run() instead of BeginInvoke().
I'm making a simple application, and I need to do some stuff, and update a progress bar. I do some stuff, wait for 1 second, and if the execution did OK, I increase a progress bar, do some other stuff, wait another second, and if OK, increase the progress bar, and so on, 7 times. By stuff, it means I'm connection to an electronic board via RS232, send some commands, it executes, and after 1 second I check if everyting did fine on the board. I need to wait 1 second for some analogic stuff on the board.
The problem is about waiting that one second. If I use Thread.Sleep(1000), the entire UI freezes (as expected) and the progress bar works in a "not synchronized" wait for the same reason.
My code is like:
progressBar1.Value = 0;
/* Do some stuff */
Thread.Sleep(1000);
/* Check the stuff */
progressBar1.Value = 1;
/* Do some stuff */
Thread.Sleep(1000);
/* Check the stuff */
progressBar1.Value = 2;
/* Do some stuff */
Thread.Sleep(1000);
/* Check the stuff */
and so on... 7 times.
The "stuff" are all different for every step.
Which is the best way to make my code to wait for 1 second?
Thanx for any help!!!
You should use the Task Parallel Library (TPL) if your version of .NET Framework supports it. This is exactly the kind of problem the TPL was designed to solve. To do that, you would start an asynchronous Task, which is an encapsulation on top of the Thread class.
Conceptually, you should be creating a new thread for the connection to the RS232 board. Your main thread (UI thread) keeps executing and does not freeze the UI. If this is a Windows application, you should read more about multithreaded programming because using a single thread in a Windows application that needs to do many things "at once" will always cause blocking and freezing issues.
If your .NET Framework version does NOT support TPL, then you should use a class called BackgroundWorker. Here's a small example to get you started: http://msdn.microsoft.com/en-us/library/ywkkz4s1.aspx
PS: My answer is based on the assumption that you are programming a Windows Forms application.
Here is a very simple, contrived WinForms example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
namespace ProgressBar
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnDoWork_Click(object sender, EventArgs e)
{
Task task1 = new Task(() => { System.Threading.Thread.Sleep(5000); });
Task task2 = new Task(() => { System.Threading.Thread.Sleep(5000); });
Task task3 = new Task(() => { System.Threading.Thread.Sleep(5000); });
Task task4 = new Task(() => { System.Threading.Thread.Sleep(5000); });
task1.ContinueWith((t) =>
{
if (t.Exception != null)
t.Exception.Handle(ex =>
{
//do something
return true;
});
progressBar.Value = 25;
}, TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith((t) => task2.Start());
task2.ContinueWith((t) =>
{
if (t.Exception != null)
t.Exception.Handle(ex =>
{
//do something
return true;
});
progressBar.Value = 50;
}, TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith((t) => task3.Start());
task3.ContinueWith((t) =>
{
if (t.Exception != null)
t.Exception.Handle(ex =>
{
//do something
return true;
});
progressBar.Value = 75;
}, TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith((t) => task4.Start());
task4.ContinueWith((t) =>
{
if (t.Exception != null)
t.Exception.Handle(ex =>
{
//do something
return true;
});
progressBar.Value = 100;
}, TaskScheduler.FromCurrentSynchronizationContext());
task1.Start();
}
}
}
Basically, just create a task for each operation; then on the continuation of that task, check the AggregateException (this is very important, as if you don't check it and there is an exception, your program will crash when the task is garbage collected). On the completion of that continuation, just fire the next task. If you need to do UI access from one of the tasks, make sure that you invoke that task from the CurrentSynchronizationContext.
The code below is a simplified example of a problem I am having. What happens upon the form loading - the For Loop will create a new task per iteration, then go as far as entering the 'if (pic.InvokeRequired)' section, but will return back to the For Loop and continue to iterate BEFORE any of the Tasks go through their respective invocations of method() after pic.BeginInvoke() is called.
What I am trying to achieve is for the invoking to complete it's second pass through method() and eventually changing the pic.BackColor to Color.Blue. I imagine this is possible, no? I've spent a couple hours searching around and could not find a satisfactory answer to this problem..
To run this code yourself, make a new WinForms Application Project (I'm using Visual Studio 2012) and add a PictureBox called 'pic'. Insert the code below.
Thank you for any and all help!
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < 5; i++)
{
Task task = Task.Factory.StartNew(() => method());
task.Wait(); //Waits for task to complete before proceeding
}
}
public delegate void delegate_method();
private void method()
{
if (pic.InvokeRequired)
{
delegate_method dm = new delegate_method(() => method());
pic.BeginInvoke(dm); //If ran once (without the loop in Form1_Load) it would call 'method()' immediately from here.
}
else
{
pic.BackColor = Color.Blue;
}
}
I can appreciate the question because in some situations blocking may be preferable to full-on async (although it's more of a last resort measure).
The answer here depends on whether your async call (the actual meat of the work which happens in the background before you get to invoke an action on the UI thread - the part of the code which I'm assuming you chose to omit for the sake of simplicity) does any UI-bound work. If it does, you're out of luck and using Application.DoEvents() is your best bet.
If, however, it does not, and you have enough control over the async code, what you can do is instead of trying to invoke the UI from within your task, pass the Action describing the work back to your UI thread (as your Task's Result) and then handle its invocation there.
Note that I've simplified the implementation of Method() as it no longer gets called from non-UI threads.
WARNING: SIMPLIFIED CODE, NOT SUITABLE FOR PRODUCTION.
using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace PicInvoke
{
public partial class Form1 : Form
{
public Form1()
{
this.InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Stopwatch sw;
// Sequential processing.
sw = Stopwatch.StartNew();
this.DoWorkAndBlockSequential();
sw.Stop();
MessageBox.Show(string.Format("Sequential work complete. Time taken: {0:0.000}s.", (double)sw.ElapsedMilliseconds / 1000.0));
// Parallel processing.
sw = Stopwatch.StartNew();
this.DoWorkAndBlockParallel();
sw.Stop();
MessageBox.Show(string.Format("Parallel work complete. Time taken: {0:0.000}s.", (double)sw.ElapsedMilliseconds / 1000.0));
}
private void DoWorkAndBlockSequential()
{
for (int i = 0; i < 5; i++)
{
var task = this.DoWorkAsync();
// Block the UI thread until the task has completed.
var action = task.Result;
// Invoke the delegate.
action();
}
}
private void DoWorkAndBlockParallel()
{
var tasks = Enumerable
.Range(0, 5)
.Select(_ => this.DoWorkAsync())
.ToArray();
// Block UI thread until all tasks complete.
Task.WaitAll(tasks);
foreach (var task in tasks)
{
var action = task.Result;
// Invoke the delegate.
action();
}
}
private Task<Action> DoWorkAsync()
{
// Note: this CANNOT synchronously post messages
// to the UI thread as that will cause a deadlock.
return Task
// Simulate async work.
.Delay(TimeSpan.FromSeconds(1))
// Tell the UI thread what needs to be done via Task.Result.
// We are not performing the work here - merely telling the
// caller what needs to be done.
.ContinueWith(
_ => new Action(this.Method),
TaskContinuationOptions.ExecuteSynchronously);
}
private void Method()
{
pic.BackColor = Color.Blue;
}
}
}
Getting error: Cross-thread operation not valid: Control 'label1' accessed from a thread other than the thread it was created on.
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
string CONNECTING = "Connecting to server...";
string GETTING_DATA = "Getting data...";
string CONNECT_FAIL = "Failed to connect!";
string CONNECT_SUCCESS = "Connection established!";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Thread t1 = new Thread(run);
t1.Start();
}
public void run() {
label1.Text = CONNECTING;
}
}
}
How do I properly fix this? I've tried using CheckForIllegalCrossThreadCalls = false; but that obviously causes errors.
I'd also like to know how I can stop the thread, since it can't be accessed anymore outside of that function.
Thanks in advance!
Try using BeginInvoke:
public void run()
{
label1.BeginInvoke(new Action(() =>
{
label1.Text = CONNECTING;
}));
}
Only the UI thread can update UI elements in a Windows Forms app. Other threads need to use BeginInvoke to update the UI.
ORGINAL: i assumed this was a WPF app and said to use this.Dispatcher, but Dispatcher isn't in Windows Forms apps.
Accessing a control from different threads
In WinForms App you can ony access directly a Control from the thread it was created.
To do such a task you will need to use InvokeRequired property of a control to see if you must use Invoke inorder to force a call of the action from the original thread.
A public method that might be accessed from any thread including the original would look like this:
public void run() {
if (label1.InvokeRequired) //Is this method being called from a different thread
this.Invoke(new MethodInvoker(()=> label1.Text = CONNECTING));
else //it's cool, this is the original thread, procceed
label1.Text = CONNECTING;
}
But if you are absolutly sure that run() method will be called only from the thread, consider not even checking if InvokeRequired and immediatly call Invoke
Further information: http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
Stopping a thread in progress
Simples is to use t1.Abort(); method of a Thread. This will throw and exception forcing it to stop where ever it was. this is great for threads that do not do any long processing so stopping it won't cause any problems.
If you do do proccesing in your thread, which means you can't just stop it in the middle then I suggest you to use a boolean that will indicate that the thread must cancel ASAP.
private bool isCancelRequired = false;
public void run() {
while(true) {
//do long processing..
if (isCancelRequired)
break;
}
}
More advanced methods: http://www.yoda.arachsys.com/csharp/threads/shutdown.shtml