I have a simly Windows Forms app with just a button and a progressbar on it.
Then I have this code:
private async void buttonStart_Click(object sender, EventArgs e)
{
progressBar.Minimum = 0;
progressBar.Maximum = 5;
progressBar.Step = 1;
progressBar.Value = 0;
await ConvertFiles();
MessageBox.Show("ok");
}
private async Task ConvertFiles()
{
await Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
System.Threading.Thread.Sleep(1000);
Invoke(new Action(() => progressBar.PerformStep()));
}
});
}
The await ConvertFiles(); returns too early, the ok messagebox already appears at about 80% progress.
What am I doing wrong?
The problem you are experiencing is not related to the async/await, which you use correctly. The await is not returning too early, just the progress bar updates too late. In other words, this is a progress bar control specific problem described in a several threads - Disabling .NET progressbar animation when changing value?, Disable WinForms ProgressBar animation, The RunWorkerCompleted is triggered before the progressbar reaches 100% etc. You can use one of the workarounds provided in those threads.
Just to be safe why not move the
MessageBox.Show("ok");
into a Continuewith so:
await ConvertFiles().ContinueWith((t) => { MessageBox.Show("ok"); });
this makes sure it only runs when the task is complete
Related
I have a pretty long task for my page, so I'm running it in a background task since I don't want it to block any postback events. Let's just assume this code looks something like this
Task task = new Task(() =>
{
for (;;)
{
Thread.Sleep(5000);
var x = (int)ViewState["progress"];
x++;
ViewState["progress"] = x;
}
});
task.Start();
As you can see, I'm storing the code in the ViewState. I have a progress bar on the page along with a timer that ticks every 5 seconds to perform a postback event. In this event, I'm updating the progress bar. The code looks something like this.
protected void Timer1_Tick(object sender, EventArgs e)
{
int perc = (int)ViewState["progress"];
//Progress bar code here
}
The progress bar wasn't updating so I tried debugging and for some reason, I'm getting entirely different values in both the cases. The timer's event gives me 0 all the time but it seems to be increasing in the loop. It's like there are two different instances of the ViewState
I've also tried locking the ViewState or using the global variable instead, but no luck.
What you have is a recipe for disaster. Thead.Sleep will suspend the thread which is very undesirable.
Disclaimer I do not recommend this technique!
You might consider using the slightly better
task = Task.Run(async () =>
{
for (var progress = 0; progress < 100; ++progress)
{
await Task.Delay(5000);
ViewState[nameof(progress)] = progress;
}
});
That said, it is still a recipe for disaster.
I have been using system.threading for a while now and I trying to wrap my head around tasks. How do you make thread safe calls to a UI control (for example a text box) from another thread using the TPL?
Here is a simple example where I want to update a text box everyone 1 second with the count of my secondary thread.
I have tried a few different methods but I can't seem to get it to work.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
await taskAsync();
}
private Task taskAsync()
{
return Task.Factory.StartNew(() => counter());
}
private void counter()
{
for (int i = 0; i < 10000; i++)
{
Task.Delay(1000).Wait();
textBox1.Text = i.ToString();
}
}
}
Is this even possible?
Thank you
Well in your current scenario I Think that a Progress would be most suitable.
I've made some alterations to your code below:
public partial class Form1 : Form
{
Progress<int> counterProgress;
public Form1()
{
InitializeComponent();
counterProgress = new Progress<int>();
counterProgress.ProgressChanged += CounterProgressUpdated;
}
private void CounterProgressUpdated(object sender, int e)
{
textBox1.Text = e.ToString();
}
private async void button1_Click(object sender, EventArgs e)
{
await taskAsync(counterProgress);
}
private Task taskAsync(IProgress<int> progress)
{
return Task.Factory.StartNew(() => counter(progress));
}
private async Task counter(IProgress<int> progress)
{
for (int i = 0; i < 10000; i++)
{
await Task.Delay(1000);
progress.Report(i);
}
}
}
Since the progress captures the current synchronization context on construction then you should be good to go as long as you create it on the UI thread.
Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.
What about something like this?
private void counter() {
for (int i = 0; i < 10000; i++) {
Thread.Sleep(1000);
// “There's never an advantage in replacing Thread.Sleep(1000); in Task.Delay(1000).Wait();”
// http://stackoverflow.com/a/29357131/4267982
Dispatcher.Invoke(() => {
textBox1.Text = i.ToString();
});
}
}
Or, as an option, another way without separate thread at all (so there's no need in synchronization):
private async Task taskAsync() {
for (int i = 0; i < 10000; i++) {
await Task.Delay(1000);
textBox1.Text = i.ToString();
}
}
How do you make thread safe calls to a UI control (for example a text box) from
another thread using the TPL?
You generall do NOT.
If you need to update the UI, then you do that using Invoke. But from TPL directly - you do not want to update the UI too often because this always is a heavy redraw and unless you make a first person shooter frame rate does not matter THAT Much. 10 updates per second are PLENTY.
In this case you may want the tasks to update a central counter (using Interlocked classes) and once that goes above a certain threshhold in changes (or using a timer running in parallel) push that into the UI.
I implemented the custom progressbar indicator in my Windows Phone 8 project. It works fine if I try to toggle the indicator with a button. But of course I want it to show up while I perform time consuming actions (filling a list with many items). But as it blocks the UI the progressbar indicator doesn't show up before the action but only afterwards. I tried .UpdateLayout() on the indicator itself and the whole page before performing modifications to the list but none of it worked.
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
// add ~100 list items
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
Is there any other way to do this?
You could offload your time consuming work to a new task and add a continuation to set progress bar visibility at the end. Here i'm using the Task Parallel Library to achieve this:
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Visible;
Task.Run(() =>
{
// Do CPU intensive work
}).ContinueWith(task =>
{
customIndeterminateProgressBar.Visibility = System.Windows.Visibility.Collapsed;
}, TaskScheduler.FromCurrentSynchronizationContext());
You should run your heavy job asynchronously (more about async at MSDN and at the Stephen Cleary Blog) - so that it won't block UI.
The very simple example where you have a ProgressBar and a heavy Task which will inform PBar about its progress can look like this: (I've subscribed the start of the method to Button Click)
private async void StartBtn_Click(object sender, RoutedEventArgs e)
{
var progress = new Progress<double>( (p) =>
{
progresPB.Value = p;
});
await DoSomething(progress); // start asynchronously Task with progress indication
}
private Task<bool> DoSomething(IProgress<double> progress)
{
TaskCompletionSource<bool> taskComplete = new TaskCompletionSource<bool>();
// run your heavy task asynchronous
Task.Run(async () =>
{
for (int i = 0; i < 10; i++) // work divided into parts
{
await Task.Delay(1000); // some heavy work
progress.Report((double)i / 10);
}
taskComplete.TrySetResult(true);
});
return taskComplete.Task;
}
I wanted to try out Threading.Task (C#) to run some work in parallel. In this simple example I have a form with progress bar and button. On click the RunParallel function is called. Without Task.WaitAll() it seems to run through fine. However, with the WaitAll statement the form shows and nothing happens. I dont understand what I am doing wrong in the setup below.
Thanks in advance.
public partial class MainWindow : Form
{
public delegate void BarDelegate();
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, EventArgs e)
{
RunParallel();
}
private void RunParallel() {
int numOfTasks = 8;
progressBar1.Maximum = numOfTasks;
progressBar1.Minimum = 0;
try
{
List<Task> allTasks = new List<Task>();
for (int i = 0; i < numOfTasks; i++)
{
allTasks.Add(Task.Factory.StartNew(() => { doWork(i); }));
}
Task.WaitAll(allTasks.ToArray());
}
catch { }
}
private void doWork(object o1)
{
// do work...
// then
this.Invoke(new BarDelegate( UpdateBar ));
}
private void UpdateBar()
{
if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
}
}
You are waiting on the UI thread. This freezes the UI. Don't do that.
In addition, you are deadlocking because Invoke waits for the UI thread to unblock.
My advice: use async/await if at all possible.
And please do not swallow exceptions. You're creating future work for yourself that way.
That's what WaitAll does, it blocks until all tasks have finished. The tasks can't finish because Invoke will run its action on the UI thread, which is already blocked by WaitAll
To make your code really run asynchronously, try something like this:
private void RunParallel() {
int numOfTasks = 8;
progressBar1.Maximum = numOfTasks;
progressBar1.Minimum = 0;
try
{
var context=TaskScheduler.FromCurrentSynchronizationContext()
for (int i = 0; i < numOfTasks; i++)
{
Task.Factory.StartNew(()=>DoWork(i))
.ContinueWith(()=>UpdateBar(),context);
}
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString(),"AAAAAARGH");
}
}
private void DoWork(object o1)
{
// do work...
}
private void UpdateBar()
{
if (progressBar1.Value < progressBar1.Maximum) progressBar1.Value++;
}
In this case, UpdateBar is called on the UI context each time a task finishes without causing any blocking.
Note, this is not production code, just a way to show how you can run a method asynchronously and update the UI without blocking. You do need to read about Tasks to understand what they do and how they work.
Using async/await in .NET 4.5+ you can write this in a much simpler way. The following will execute DoWork in the background without blocking, but still update the UI each time a Task finishes.
private async void button1_Click(object sender, EventArgs e)
{
int numOfTasks = 8;
progressBar1.Maximum = numOfTasks;
progressBar1.Minimum = 0;
try
{
for (int i = 0; i < numOfTasks; i++)
{
await Task.Run(() => DoWork(i));
UpdateBar();
}
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString(), "AAAAAARGH");
}
}
await tells the compiler to generate code to execute anything below it ont the original (UI) threadwhen the task to its right finishes:
In doWork you call this.Invoke(...) which waits for UI thread to process messages. Unfortunatelly you UI thread is not processing messages because it is waiting for all doWork(...) to finish.
Easiest fix is to change this.Invoke to this.BeginInvoke (it will send messages but no wait for them to be processed) in doWork.
Although, I have to admin it is still not by-the-book as UI should not wait for anything.
Simple pattern (pre async/await era):
Task.Factory.StartNew(() => {
... work ...
})
.ContinueWith((t) => {
... updating UI (if needed) ...
}, TaskScheduler.FromCurrentSynchronizationContext());
RunParallel blocks until all the tasks are completed. Use a different mechanism to notify the UI.
I am studying parallelism and would like to know which way do you recommend for me to access other thead elements, for example, imagima I'll fill a combobox with some names, query the database I would do in parallel but I could not do a combobox.add (result) from within the task, which way do you recommend me?
a simple example to understand my question:
private void button1_Click (object sender, EventArgs e)
{
Task task = new Task (new Action (Count));
task.Start ();
}
void Count ()
{
for (int i = 0; i <99; i + +)
{
Thread.Sleep (1);
progressBar1.Value = i;
}
}
time to pass the value for the progressbar result in error
If you want to schedule a task that access UI controls, you need to pass the current synchronization context to the scheduler. If you do that the scheduler will make sure your task is executed on the correct thread. E.g.
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => {
// code that access UI controls
}, uiScheduler);
For more info see http://msdn.microsoft.com/en-us/library/dd997402.aspx
You cannot access controls on another thread directly. You must invoke them first. Read this article: http://msdn.microsoft.com/en-us/library/ms171728.aspx
This is about what is would look like if you took the article and translated it for your own use: (NOT TESTED)
delegate void SetProgressBarCallback();
private void SetProgressBar()
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.progressBar1.InvokeRequired)
{
SetProgressBarCallback d = new SettProgressBarCallback(SetProgressBar);
this.Invoke(d);
}
else
{
for(int i=0; i<99; i++)
{
Thread.Sleep(1);
progressBar1.Value = i;
}
}
}
Just a quick note... the UI in WinForms can only be updated from the UI thread. Perhaps you should consider using Control.Invoke to update your progressBar1.
Ryan's answer was correct but he put the sleep inside the invoke, that caused the program to hang. Here is a example that uses the same thing he did but it does not put the sleep in the invoke.
private void button1_Click (object sender, EventArgs e)
{
Task task = new Task (new Action (Count));
task.Start ();
}
void Count ()
{
for (int i = 0; i <99; i + +)
{
Thread.Sleep (1);
if(progressBar1.InvokeRequired)
{
int j = i; //This is required to capture the variable, if you do not do this
// the delegate may not have the correct value when you run it;
progressBar1.Invoke(new Action(() => progressBar1.Value = j));
}
else
{
progressBar1.Value = i;
}
}
}
You must do the int j = i to do variable capture, otherwise it could bring up the wrong value for i inside the loop.