Task causing strange behavior in form_load event - c#

I have a task that runs in the form_load event of a usercontrol in winforms:
private void ucDeviceInsert_Load(object sender, EventArgs e)
{
System.Threading.Tasks.Task getTBox = System.Threading.Tasks.Task.Run(async () =>
{
await AVeryLongRunningProccess();
});
pbImage.Image = Properties.Resources.Remove;
getTBox.Wait();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
private void tmrDeviceInsert_Tick(object sender, EventArgs e)
{
Next();
}
I change the image of the picture box to inform the user the progress of the long running process. That part works fine, however the button doesn't show, and the timer never starts. I've stepped through the code, and I can confirm that it is running without any problems, which makes this even more baffling. Any ideas what would be causing this issue?

Task.Run is for pushing CPU-intensive work off the UI thread. Since you're calling an asynchronous method, I suspect it's not CPU-intensive.
So, you can just use async and await:
private async void ucDeviceInsert_Load(object sender, EventArgs e)
{
pbImage.Image = Properties.Resources.Remove;
await AVeryLongRunningProccess();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
Note that at the await, the UI is shown and the user can interact with it (that's the point).

getTBox.Wait() is going to try to complete that task synchronously. Therefore, the rest of the code after it won't happen until after the task completes.
I'd think you don't want your task to run synchronously at all, but rather handle its completion asynchronously, something like this:
getTBox.ContinueWith(() => updateStatusInUI());

Related

Need to click button twice to have an effect

The application is a machine control, so it needs access to ui to show status etc. (I know, goes against the recommendation to separate UI and work code, but it is what it is, at least for now). The issue boils down to this: When one button event handler is not finished, another button needs to be clicked twice. First click gives the focus to the button, next click fires the event.
Here is the issue simplified to extreme. There are two buttons and a label. Stop button needs two clicks to stop the machine:
bool Stop = true;
private void Start_button_Click(object sender, EventArgs e)
{
RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
private void RunMachine()
{
Stop = false;
Status_label.Text = "Running";
do
{
Application.DoEvents();
Thread.Sleep(50);
}
while (!Stop);
Status_label.Text = "Stopped";
}
How can I make the button to react to the first click?
DoEvents() is bad. Don't use it.
If you have to use it (e.g. as workaround), then you are adding technical debt and likely to pay in the future, similar to your case.
A better approach is to run work inside the task and use cancellation token, but in your case the minimum modification required is this (add async modifier to a method):
while (!Stop)
{
await Task.Delay(50);
// or
await Task.Run(() => Thread.Sleep(50));
}
The UI should be responsive now.
The latter is simulating synchronous code, put it instead of Sleep, don't forget to invoke if there you have to modify UI.
Thank you! I wasn't aware of the implications of Doevents, and using async and await is just as simple. I added a counter to show myself that the toy example is doing what I think it is. To make the answer complete and to help other noobs like me that might search answers for the same issue, here is the full example again. This works as wanted (stops with one click) and doesn't leave the RunMachine() running if the main form is closed without clicking stop. (My real application has enough code in the form closing event to prevent that, but I certainly wasn't aware of the trap.)
bool Stop = true;
private async void Start_button_Click(object sender, EventArgs e)
{
await RunMachine();
}
private void Stop_button_Click(object sender, EventArgs e)
{
Stop = true;
}
internal async Task RunMachine()
{
Status_label.Text = "started";
Stop = false;
int i=0;
do
{
await Task.Delay(500);
Status_label.Text = i.ToString();
i++;
} while (!Stop);
Status_label.Text = "Stopped";
}

WPF Application Indefinite progress bar doesn't show but processes are running on background

As I got the processes to work I ran onto possibly a Threading problem when I was trying to add a ProgressBar to give the user some clue that the process hasn't crashed. Since this is enough, an Indefinite ProgressBar in just fine for me.
<ProgressBar x:Name="ProcessProgress" Minimum="0" Maximum="100" Visibility="Hidden" IsIndeterminate="False" Width="305" Height="20" Margin="240,214,248,10"></ProgressBar>
I tried to initiate a determinate length progress bar which is not visible before the Start button is clicked :
private void Start_Click(object sender, RoutedEventArgs e)
{
...
//process starts here
var fP = new FileProcessor();
ProcessProgress.IsIndeterminate = true;
ProcessProgress.Visibility = Visibility.Visible;
...
//finally show the progressbar as full when done
ProcessProgress.IsIndeterminate = false;
ProcessProgress.Value = ProcessProgress.Maximum;
}
It just runs the whole process and my bar doesn't show up.
How can I spawn a progress bar during the process ?
The problem is that all of your code runs in the UI thread. The UI has no chance to update itself before the entire process finishes.
You can use Task.Run() to run a heavy job in the background without blocking the UI. You can use the await keyword to await that task to complete without blocking the UI. Once the task completes, you are back in the UI thread where you can modify the UI again.
A quick and dirty fix for this event handler would be :
private async void Start_Click(object sender, RoutedEventArgs e)
{
ProcessProgress.IsIndeterminate = true;
ProcessProgress.Visibility = Visibility.Visible;
...
await Task.Run(()=>
{
//process starts here
var fP = new FileProcessor();
...
});
//We are back in the UI thread, we can modify the UI
ProcessProgress.IsIndeterminate = false;
ProcessProgress.Value = ProcessProgress.Maximum;
}
No need to use Invoke to get back to the UI thread, that's the job of await itself.
A note about async void. It's ONLY meant for event handlers or similar methods. You can't await an async void method which means you can't even get any exceptions if something goes wrong. Asynchronous methods that return nothing should have the async Task signature.
If you want to report progress you can use the IProgress interface and the Progress class as explained here. The Progress class will call a callback or raise an event on the thread it was created. The payload can be any class, not just a percentage
It's best to move the reporting code to separate methods, to keep the button handler clean. The code can look like this :
//The progress class
class FileProgress
{
public string FileName{get;set;}
public int Progress{get;set;}
public string Message{get;set;}
public FileProgress(string fileName,int progress,string message)
{
FileName=filename;
Progress=progress;
Message=message;
}
}
//In the form itself
private void ReportStart()
{
ProcessProgress.IsIndeterminate = true;
ProcessProgress.Visibility = Visibility.Visible;
}
private void ReportEnd()
{
ProcessProgress.IsIndeterminate = false;
ProcessProgress.Value = ProcessProgress.Maximum;
}
private void ReportProgress(FileProgress progress)
{
ProcessProgress.Value =progress.Progress;
PanelStatus.Text = $"Working on {progress.FileName} : {progress.Message}";
}
The event handler can now look like this :
private async void Start_Click(object sender, RoutedEventArgs e)
{
ReportStart();
IProgress<FileProgress> progress=new Progress<FileProgress>(ReportProgress);
...
await Task.Run(()=>
{
//process starts here
var fP = new FileProcessor();
foreach(var file in someFiles)
{
progress.Report(new FileProgress(file,0,"Starting");
//Do some processing
progress.Report(new FileProgress(file,100,"Finished");
}
...
});
//We are back in the UI thread, we can modify the UI
ReportEnd();
}

wpf c# background worker works when execution finished

I have a wpf application where I want to start a loading indicator before a task and end after task done. But the indicator starts after the task executes.
What I am trying is as follows.
private void RunAllScriptsChildwdwBtnOK_Click(object sender, RoutedEventArgs e)
{
worker.RunWorkerAsync(); // this supposed to start progress bar
_RunAllScripts_Click();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke(() =>
{
... Start loading indicator
});
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
... End loading indicator
}
But loading indicator starts and ends (as supposed in worker events) only after
_RunAllScripts_Click(); method execution is complete.
(I found that after unsubscribing from worker_RunWorkerCompleted event, progress bar starts and stays as is because no code to end it).
Also I want to add that, breakpoint hits worker_DoWork method before the execution, but UI updates after execution as I indicated above.
Thanks for all help you will be able to provide.
If i was you i would use the async + await keyword for this
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// this is where you would enable your indicator
Button.IsEnabled = false;
await Task.Run(
() =>
{
// this is where you put your work, which should be executed in the background thread.
Thread.Sleep(2000);
});
// this is where you would disable it
Button.IsEnabled = true;
}
Using async/await will work. The await keyword will allow you to run work without affecting/blocking the UI thread (allowing message pumping to still occur). Once the work has finished, any code after the await keyword will execute.
Note that I have also wrapped the await work in an InvokeAsync call, as it appears that additional work you are doing required UI thread access.
private async void RunAllScriptsChildwdwBtnOK_Click(object sender, RoutedEventArgs e)
{
//TODO ... Start loading indicator
await Task.Run(async ()=>
{
await Application.Current.Dispatcher.InvokeAsync(()=>
{
_RunAllScripts_Click();
});
});
//TODO ... End loading indicator
}
Dear kind people helping me about this subject, thank you all.
This works for me, hope it works for all.
BackgroundWorker bwTestAll = new BackgroundWorker() { WorkerReportsProgress = true };
bwTestAll.DoWork += new DoWorkEventHandler(TestAll);
bwTestAll.RunWorkerCompleted += TestAll_RunWorkerCompleted;
//this is where I initialize my loading ring and other stuff and marshall background
//worker to do the main work
Dispatcher.Invoke(new Action(() =>
{
EnableLoading = true;
RunAllScriptsTest.IsEnabled = false;
}), DispatcherPriority.ContextIdle);
bwTestAll.RunWorkerAsync();
//this is my main work
void TestAll(object sender, DoWorkEventArgs e)
{
presenter.RunAllScripts(true);
}
//this is where I do my post-work stuff
private void TestAll_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
Dispatcher.Invoke(new Action(() =>
{
/
EnableLoading = false;
RunAllScriptsTest.IsEnabled = true;
DbExecGrid = this.ExecutionResults;
ShowOrHideExecGrid(this.EnableOrDisableGrid);
}), DispatcherPriority.ContextIdle);
}
*Please Notice that Dispatcher with "DispatcherPriority.ContextIdle" works for me.

Is it possible to change views, once during the start of an event handler and once during end?

I get data from database on a click.
I have an event handler which when triggered should show "data retrieving..." in status bar and should change to "Ready" again just before the event handler ends.
But the text updates only once, the second Ready one. How is it generally done?
private void Next_Click(object sender, RoutedEventArgs e){
this.footerText = "Waiting for dataRetreival";
someRandomTimeTakingMethod(); //Gets Data from DB.
this.footerText = "Ready";
}
Even though code executes line 2, the view updates only when the function is over, ie only the second one actually works.
You should put your data-intensive work on a background thread so the UI can update properly. This provides the best user experience.
To elaborate on FZysset's answer with some code...
private async void Next_Click(object sender, RoutedEventArgs e)
{
footerText.Text = "Waiting for dataRetreival";
IsEnabled = false;
await SomeRandomTimeTakingMethodAsync();
IsEnabled = true;
footerText.Text = "Ready";
}
private async Task SomeRandomTimeTakingMethodAsync()
{
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(2, 5)));
// or await Task.Run(() => { ... });
}
The above example allows you to leverage await/async that was introduced in .NET 4.5. Notice how nicely it flows? No nonsense!
We're putting stuff onto the background thread so the UI can remain unblocked (thus it will show your updates to your status bar and allow user interaction.) Of course, you have to be careful not to update anything on the UI from your background thread.
If you are using an older version of .NET, you can just use TPL without async/await:
private void Next_Click(object sender, RoutedEventArgs e)
{
footerText.Text = "Waiting for dataRetreival";
IsEnabled = false;
Task.Factory.StartNew(() =>
{
SomeRandomTimeTakingMethod();
}).ContinueWith(t =>
{
IsEnabled = true;
footerText.Text = "Ready";
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private void SomeRandomTimeTakingMethod()
{
Thread.Sleep(TimeSpan.FromSeconds(new Random().Next(2, 5)));
}
Two important things to note about the latter example:
You must provide TaskScheduler.FromCurrentSynchronizationContext() to the ContinueWith call, or you will encounter exceptions because the continuation is not on the UI thread. You must get the context in a method that isn't running on a background thread.
You will want to check for exceptions on the Task object in your ContinueWith.
This example is very rudimentary though. If you were to have a bunch of background operations kicked off with click handlers, you'd want to give yourself some helper classes/services to make life easier. (And investigate MVVM, which I cannot tell if you are using.)
A colleague of mine gave a presentation on using various asynchronous patterns in C# and .NET. You can check it out here: https://github.com/mtusk/TplLunchAndLearn
That's because you're "someRandomTimeTakingMethod" is launched on the UI Thread. Therefore it will not update the view until it is finished.
To go around this you have the following possibilities :
Make your method "someRandom..." asynchronous with a task, and use the await operator : http://msdn.microsoft.com/en-us/library/hh191443.aspx
Launch your randomTimeTaking method into a thread, and launch an event when your execution is finished, to update the footer text
I strongly recommend you the first option, for some sample : http://msdn.microsoft.com/en-us/library/hh873191.aspx
You need to run those lines asynchronously. You can do that using the Task class:
private void Next_Click(object sender, RoutedEventArgs e){
Task.Factory.StartNew(() => footerText = "Waiting for dataRetreival");
someRandomTimeTakingMethod(); //Gets Data from DB.
Task.Factory.StartNew(() => footerText = "Ready");
}
There is one way to do it using Dispatcher. The original post is here.
The code is:-
private void Next_Click(object sender, RoutedEventArgs e){
UpdateUI("Please wait for data retrieval", delegate() { someRandomTimeTakingMethod(); });
this.footerText = "Ready";
}
public delegate void NoArgsDelegate();
public void UpdateUI(string description, NoArgsDelegate operation)
{
this.FooterText= description;
DispatcherFrame frame = new DispatcherFrame();
DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, operation);
dispatcherOperation.Completed += delegate(object sender, EventArgs e)
{
frame.Continue = false;
};
Dispatcher.PushFrame(frame);
}
If my understanding is right, this uses Asynchronous programming, not different thread. The thread will update UI first and then call the someRandomTimeTakingMethod().

Calling an async method with c#5.0

I do some tests with the new asynchronous pattern of C# 5.0 (async/await) I have a problem with understanding how the asynchronous methods are called.
Considering this code :
private async Task<string> DownloadAsync()
{
progress.ProgressChanged += (s, e) =>
{
progressBar1.Value = e.value;
};
return await DownloadSomething(myurl, progress);
}
private async void CallDownloadAsync()
{
string text = await DownloadAsync();
progressBar1.Value = 0;
label1.Text = "Done!";
}
private void button4_Click(object sender, EventArgs e)
{
CallDownloadAsync();
}
So, this code works very well. When I clic the "button4" a downloading task begins and my ProgressBar is updated correctly.
But, I'd like to compact my code a little bit more by removing CallDownloadAsync() method like this :
private void button4_Click(object sender, EventArgs e)
{
new Action(async () =>
{
string result = await Task.Run<string>(() => DownloadAsync());
}).Invoke();
label1.Text = "Running...";
}
So here, I want to directly initiate an action which calls the DownloadAsync method but when I hit my Button4 I have a Cross-thread operation not valid on the progressBar. So I do not understand what is the main difference between the Action() and the call of my CallDownloadAsync() method.
You may find my async/await intro helpful. In particular, an async method does not run on a background thread; Task.Run is used to run something on a background thread, hence the difference in your code.
In general, you should avoid async void, unless you're writing an async event handler. Like this:
private async void button4_Click(object sender, EventArgs e)
{
label1.Text = "Running...";
string result = await DownloadAsync();
progressBar1.Value = 0;
label1.Text = "Done!";
}
The difference is that in former case you call CallDownloadAsync() from UI thread (context).
In the latter case, DownloadAsync() is called from the initiated Task which is generally executed in a different thread created by TPL (Task Parallel Library) out of UI thread or threads created from it.
In WPF, UI components can be accessed only by a dedicated UI thread or (its children) threads created from under it (i.e with the same UI context).

Categories