I'd like to have a indeterminate progress bar going while I'm doing a function. The problem is that while the function is working the UI freeze until the end so I end up with:
void Button1_Click(object sender, RoutedEventArgs e)
{
progressBar1.IsIndeterminate = true;
Task.Factory.StartNew(() =>
{
scrape();
});
}
The thing is that as for backgroundworker, my scrape function don't trigger. I put just the scrape(); onclick it work just fine. The scarpe is something like:
void scrape()
{
string url = "www.site.com";
var web = new HtmlWeb();
var doc = web.Load(url);
foreach (HtmlNode node in doc.DocumentNode.SelectNodes("//p[#class='bio']"))
{
//scrape things
}
progressBar1.IsIndeterminate = false;
}
The progress bar go in indeterminate but the scrape() don't trigger and the bar still in indeterminate state.
Any help?
your probably getting an exception in the code that's running on the background thread.
either catch your exception by registering to
AppDomain.UnhandledException
Or more specifically UnobservedTaskException which is like UnhandledException but specific to exceptions thrown from Tasks.
or make your method async and await the operation in a try catch clause :
async void Button1_Click(object sender, RoutedEventArgs e)
{
progressBar1.IsIndeterminate = true;
try
{
await Task.Factory.StartNew(() =>
{
scrape();
});
}
catch(AggregateException ae)
{}
finally
{
progressBar1.IsIndeterminate = false;
}
}
You should make your scrape method async and then await it in the button1_click and keep the progressBar1.IsIndeterminate = false; call in the button1_click method as you are not allowed to change that from another thread.
You are calling assigning a property from another thread. WPF does things to prevent cross thread accesses like this to keep all the UI logic in the UI thread (the main thread). If you want, you can call a method from the UI thread using a Dispatcher. You can access the dispatcher from any control using myControl.Dispatcher.Invoke(MyMethod).
It's also possible to check if you need to call a method from the dispatcher or not. Use if (myControl.Dispatcher.CheckAccess()). If this value is true, you are on the UI thread and you don't need to invoke from the dispatcher.
Related
I need to run a task to check the connection.
My windows should not be frozen during this check.
So I start the Task, and close my window at the end of this task.
But this returns an exception: InvalidOperationException:'The calling thread cannot access this object because another thread owns it'.
Like this :
private void Window_ContentRendered(object sender, System.EventArgs e)
{
Task.Run(() =>
{
ConnectionState = false;
if (NetworkTools.CheckGlobalConnection() == (ConnectionStatus.NetworkConnectionSuccess, ConnectionStatus.ServerConnectionSuccess))
{
ConnectionState = true;
}
this.Close();
});
}
How do I close my window at the end of the task without freezing it and without having this exception ?
(I have a progress bar that rotates indefinitely)
Or you could just use async await. Task.Run will offload and the await will create a continuation on current SynchronizationContext. In turn giving control back to the UI and closing on the right thread.
private async void Window_ContentRendered(object sender, System.EventArgs e)
{
await Task.Run(() =>
{
ConnectionState = false;
if (NetworkTools.CheckGlobalConnection() == (ConnectionStatus.NetworkConnectionSuccess, ConnectionStatus.ServerConnectionSuccess))
ConnectionState = true;
});
this.Close();
}
Also as noted, Calling ConfigureAwait(false), would not be the right thing to do in this case
Use Dispatcher to queue window closing logic on the unique UI thread.
Something like
Dispatcher.Invoke(
() =>
{
// close window here
});
Whatever is passed into .Invoke(...) as a delegate, is invoked on the UI thread and hence has right to access all UI elements. It is common (and the only correct) way to deal with UI-mutations within non-UI threads.
As an alternate method you can use ContinueWith
private void Window_ContentRendered(object sender, System.EventArgs e)
{
Task.Run(() =>
{
// Your code
}).ContinueWith((tsk) =>
{
this.Close();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
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();
}
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());
I have something doing background and I want to show a messagebox if something wrong happens.
First I tried
var _timer = new System.Threading.Timer((o) =>
{
if(!DoCheck()){
Messagebox.Show("The message");
}
});
Nothing wrong happens.
And I have another job to be done in background, and it's invoked by button click, like
private void button3_Click(object sender, EventArgs e)
{
var task = new Task(() =>
{
DoWork();
Messagebox.Show("Done");
});
_task.Start();
}
A System.Reflection.TargetInvocationException is thrown when the MessageBox is shown.
I have also tried this.Invoke, it raised an exception, too.
My question is:
Is the first case safe?
How to make the second case work?
No. You should preferably be using System.Windows.Forms.Timer in a WinForms application. The documentation specifically calls this out:
This Windows timer is designed for a single-threaded environment where UI threads are used to perform processing. It requires that the user code have a UI message pump available and always operate from the same thread, or marshal the call onto another thread.
Furthermore, it depends on what your DoCheck method is doing. We will need to see the code of that method.
Use the BeginInvoke method:
var form = this;
var task = new Task(() =>
{
DoWork();
form.BeginInvoke(() =>
{
MessageBox.Show("Done");
});
});
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().