Why is this async method blocking the UI thread? - c#

I am struggling with an issue which is raised by this piece of code:
private int FPS = 60;
void WebView_LoadCompleted(object sender, NavigationEventArgs e)
{
WebviewContentWorker();
}
private async void WebviewContentWorker()
{
WebViewBrush wvb = new WebViewBrush();
wvb.SetSource(WebView);
wvb.Redraw(); //we must redraw at least once before collapsing the WebView
WebView.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
while (true)
{
webViewContent.Background = wvb; //webViewContent is a canvas
await Task.Delay(1000 / FPS);
wvb.Redraw();
}
}
What I'm trying to achieve here is to find a workaround for XAML's WebView which I find very sloppy. I want to be able to draw things on top of it but I can't so what I'm basically doing is taking snapshots of the WebView (using the WebViewBrush) repeatedly (based on the int FPS field) and then setting the Background property of the canvas named "webViewContent" with this snapshot. The aim is to have animations displayed on the canvas while still being able to draw on top of it (if I don't do these fast snapshots, the canvas will display a still image).
It is working fine now (I successfully redirect any Tapped event to the inside of the WebView so that clicks on buttons/links/... are properly handled) but it is somehow laggy. The slow bit being wvb.Redraw() I was wondering how I could improve the performance of my thread. It looks like the UI is responsive during the Task.Delay but is blocked otherwise...
Any input/advice is very welcome!
Edit:
Here is how I timed the Redraw call (which I believe is what causes the problem since removing it makes the application very responsive):
while (true)
{
webViewContent.Background = wvb;
await Task.Delay(1000 / FPS);
sw.Reset();
sw.Start();
wvb.Redraw();
sw.Stop();
System.Diagnostics.Debug.WriteLine(sw.Elapsed.TotalMilliseconds);
}
Which gives me these results in the output window:
0,094
0,058
0,041
0,053
0,057
0,038
0,032
0,033
0,032
0,038
0,035
0,03
0,042
0,028
0,044
0,031
0,033
0,029
0,034
0,03
0,052
0,029
So not so much after all...

It looks like the UI is responsive during the Task.Delay but is blocked otherwise...
Well yes. That's exactly what's happening. Task.Delay is the only chance you're giving the UI thread to work. Your asynchronous method is executing on the UI thread - as soon as the "delaying" task completes, you'll end up with a continuation waiting to execute on the UI thread which will redraw and then delay again.
Fundamentally, if your Redraw method is too slow to be called ~60 times per second, you need a different approach.
It's very important to understand that async doesn't put the method onto a different thread - it just allows you to act asynchronously. (Your description and title suggest that you expected your method not to be using the UI thread for any significant time.)
Additionally, as Stephen Cleary says, using a DispatcherTimer is a generally-better way of executing code periodically on the UI thread.

Task.Delay is not particularly efficient for repeatedly doing many short timeouts. You'll generate a lot of garbage.
I would recommend using a dispatcher timer or similar for this scenario.

Related

How to make multiple labels appear at different times C#

I tried to use this code and when I pressed my button they all appeared at the same time after what I think is all the thread.sleep time combined
private void guna2Button2_Click(object sender, EventArgs e)
{
Thread.Sleep(1000);
label18.Text = "Step1";
Thread.Sleep(4000);
label28.Text = "Step2";
Thread.Sleep(1500);
label27.Text = "Step3";
Thread.Sleep(6590);
label26.Text = "Step4";
}
I think you want to update the label after the mentioned time.
Try this:
private async void guna2Button2_Click(object sender, EventArgs e)
{
await Task.Delay(1000);
label18.Text = "Step1";
await Task.Delay(4000);
label28.Text = "Step2";
await Task.Delay(1500);
label27.Text = "Step3";
await Task.Delay(6590);
label26.Text = "Step4";
}
they all appeared at the same time
When a windows forms program is running, there is by default a single thread executing all the code. That thread has a really important job; processing all the events from the user and drawing the UI. It only does those things when it is not processing your code that you write in a button click handler etc. If it's doing your code it's not doing those things. When the user clicks a button it stops doing those things and starts doing your things.
It's really important for a good user experience that you don't occupy its attention for very long - let's say you should try and keep to less than a hundred milliseconds. If your code captures its attention for too long the user will notice the UI jams/freezes and if the operating system notices it will fade the window and mark it as "not responding"
Your code as it is captures the thread for a very long time, and prevents it from going back to its normal work of drawing the UI. Thread.Sleep makes it sit doing nothing for the duration of the timeout, and it really does just sit and wait for the time to finish. Then you make a label visible, but the thread won't draw that as visible until you release it from your code and let it go back to doing its work, then you sleep it again. This basically means after 15 or so seconds your code will finally reach the end of the button click handler and the thread will go away, back to drawing your UI with a to-do list of "draw these 5 labels which were make visible whilst you were busy" so they all appear at once
It's an interesting learning point, embodied in the answer Amit posted, and the way try to do things now; when you use async, and await Task.Delay(...) this is very different to Thread.Sleep. Whenever a thread encounters await X it kicks off X as a background operation and crucially, it goes back to what it was doing before it started doing your code which in this case is drawing the UI. When the background operation is done it will be called back to pick up from where it left off.
This means those labels appear gradually, because the thread that is processing your code is spending most of its time actually not doing your code but drawing the UI so it can draw labels as visible very soon after you made them visible rather than being kept busy by your code all the way to the point where your code ends
The lesion you're learning here is fairly important for a lot of your future code. Using async/await is a good way of making better use of resources; threads are resource-expensive things to have sitting around doing nothing. By making better use of fewer threads (by making them fill their time with as many jobs as possible when they would otherwise be sitting around doing nothing) our programs scale up better. This means when you're kicking off an operation to download a 1gb file that will take 30 seconds, you should definitely do it with an asynchronous download approach, otherwise you'll jam your UI/hang your web server thread etc. in the case of a user app, it's just a bit annoying to have the Ui freeze all the time but in the case of a busy webserver, it having to start new threads to service new requests just because all its existing threads are jammed doing nothing means that it can run out of resources pretty quickly

Could a BeginInvokeOnMainThread method be looping and causing a memory leak?

I have an application that works but after a while when I debug on my iPhone it hangs the phone and the only way I can recover is a hard reset of the button on the side and the home button.
First of all, could that be because my application has a memory leak?
Here's the code for the application. In particular, I am looking at the BeginInvokeOnMainThread method. Can someone tell me if they can see if there could be any problems with the way that it is implemented? Also, what's the purpose of the .ContinueWith((arg).
namespace Japanese
{
public partial class PhrasesFrame : Frame
{
CancellationTokenSource cts = new CancellationTokenSource();
public PhrasesFrame(PhrasesPage phrasesPage)
{
InitializeComponent();
this.phrasesPage = phrasesPage;
AS.phrasesFrame = this;
Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token).ContinueWith((arg) => { }));
}
public void Disappearing()
{
cts.Cancel();
}
public async Task ShowCards(CancellationToken ct)
{
AS.cardCountForSelectedCategories = App.DB.GetCardCountForSelectedCategories();
while (!ct.IsCancellationRequested)
{
await Task.Delay(500);
}
}
}
}
ContinueWith
First, let's address your question about .ContinueWith((arg) => { })). ContinueWith tells more code to execute once the original Task has completed. In our case, the code inside of ContinueWith will run once Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token) has finished.
In this case, there is no code inside of ContinueWith, so we can remove it.
Freezing
Yes, I can see that this code has potential to freeze the UI.
BeginInvokeOnMainThread will queue an Action to run on the Main Thread (also known as the UI Thread). The Main Thread is constantly listening for user input (tapping a button on the screen, pinch-to-zoom, etc.), and if this thread is busy doing a long-running task, it will not be able to respond to a user's input until it has finished; thus your app will appear frozen.
The code await Task.Delay(500); is being called by the Main Thread. We are thus telling the Main Thread to freeze itself for 500 milliseconds, and looping that indefinitely.
One solution would be to wrap this code in Task.Run which would put it in a background-thread and free the Main Thread to listen/respond to user input.
Task.Run(async () =>
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(500);
}
}
More Threading Recommendations
Only use BeginInvokeOnMainThread when you need to update the UI. 99% of code can run on a background thread with no problems. The 1%, however, is code that updates the UI; any code that updates the UI must be run on the Main Thread.
If a task that takes longer than the refresh rate of the screen to execute, perform it on a background thread. For example, if the screen's refresh rate is 60Hz, it is updating 60-times per second, every 16.7ms. So if we have a block of code that takes 20ms to execute, we need to execute it on a background thread to ensure that we don't freeze the app and drop any frames.
The code above looks like it is accessing a database, which I would highly recommend moving to a background thread like so
await Task.Run(() => AS.cardCountForSelectedCategories = App.DB.GetCardCountForSelectedCategories());
First, if you are concerned about a memory leak, you can check for low-memory warnings in the device logs (accessible through XCode), or override the ReceiveMemoryWarning method in your app delegate to log an error.
Secondly, there's nothing obviously wrong with the way you're calling BeginInvokeOnMainThread that would cause a leak. The ContinueWith is a no-op that doesn't affect the operation of the code - I'm guessing it's there to avoid a compiler warning that you're not awaiting the task.
Thirdly, if you suspect that this code is causing a leak, you should use logging and/or breakpoints to confirm that it's behaving as expected. Is the task correctly cancelled when you navigate away from the page? Do you see multiple instances of of the ShowCards task running? If this code turns out to be behaving correctly, then the source of the hang lies elsewhere in your app. For instance, it looks like you're making a database call twice a second - maybe it's not cleaning up resources properly.

WPF multi-thread progress update

I'm need to display some form of feedback to the user, while a small process (7-10 seconds) takes place in the background.
I had no issues in the past using separate threads and BackgroundWorkers in Windows Forms, but its proving difficult in WPF.
I have read many articles, in this respect, and how I should be using dispatchers in WPF to start a new thread, etc. However, when I try to use a BackgroundWorker to display a form of waiting image feedback, it simply remains static.
I don't believe that it matters, but it uses mui from FirstFloor (https://github.com/firstfloorsoftware/mui).
I'm trying to use the built-in ProgressRing feature (which works no problems when run within the same thread and there are no other major tasks running in the background.
Adding a BackgroundWorker, brings an exception due to cross thread access of objects, even though many blogs states that BackgroundWorks in WPF are cross thread aware and safe to run.
The following is the closest code that generates what I need.
private async void MyTaskProcess()
{
await Dispatcher.BeginInvoke(DispatcherPriority.Send, new ThreadStart(() =>
{
try
{
//Update the waiting ring image
ProgressRing.IsActive = true;
}
catch
{
ProgressRing.IsActive = false;
MessageBox.Show("Exception Thrown");
}
}));
await Dispatcher.BeginInvoke(DispatcherPriority.Background, new ThreadStart(() =>
{
try
{
//Run the main MS Excel export function
Export2Excel();
ProgressRing.IsActive = false;
}
catch
{
MessageBox.Show("Exception Thrown");
}
}));
}
Any feedback is appreciated.
The way you do this in a modern WPF application is to start a new Task in which you do the work; under the covers this will perform the work on a thread pool thread:
Task.Factory.StartNew(this.DoWork)
Now in DoWorkto report progress you InvokeAsync back to the main thread whenever the porgress count changes:
void DoWork()
{
foreach(var item in this.WorkItems)
{
// Do something
// Report Progress
++progress
Application.Current.Dispatcher.InvokeAsync(() => this.Progress = progress);
}
}
Adding a BackgroundWorker, brings an exception due to cross thread access of objects, even though many blogs states that BackgroundWorks in WPF are cross thread aware and safe to run.
BackgroundWorker works fine with WPF, as long as you create and start the BGW on the UI thread. (As a side note, BGW has the same restriction on Windows Forms). As other commenters have noted, the proper way to do progress updates with BGW is using ReportProgress, not Dispatcher.
However, I'd recommend using the newer Task.Run with IProgress<T> for progress updates. I have a blog post that compares/contrasts the old BGW progress updates with the new IProgress<T>-based progress updates.
It's difficult to say what your code should look like, since the code you posted doesn't actually run anything on a background thread. In particular, if Export2Excel must be run on the UI thread, and that's all your work is doing, then there's no point in using BGW or Task.Run at all, since nothing can run on the background thread anyway.
you go, to the below link written by me and read carefully.I hope you will definetily solve your problem:
Threads in WPF

Application Freeze in loop when using Sleep

I have an application that I made to connect to a device using telnet, the application freeze/crash when I started it .. I have System.Threading.Thread.Sleep(3000); in couple locations of the application. I was wondering, is there a way to have the application active and buttons are useable but the some operations are only impacted by the sleep operation?
Thanks in advance.
Don't use Thread.Sleep, especially on the UI thread. This will cause a hang - by design.
Since you're using .NET 4.5, you can use await Task.Delay(3000); to asynchronously "sleep", which won't block the UI. However, this is typically a sign of a poor design - "waiting" is something that really shouldn't need to happen in a UI application in general. There are typically better approaches, such as using await on the asynchronous operation for which you're waiting, etc.
This requires a bit of an explanation. There are some threads that are special in this case the ui thread where the rendering of your Ui happens and the events from the input devices are handled. If this thread spends time doing any calculations windows will state that your application has frozen. Since you are using Thread.Sleep on it you get this result.
Articles to understand the problem
http://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd744765(v=vs.85).aspx
Recommended Solutions
On the Button press dispatch a thread that goes and does the work that you need to happen. On the meantime change the cursor for the mouse to indicate that work is happening or show a progress bar. Once it finishes you can fire(dispatchet) an event that changes the ui.
I would do something similar to:
// The Work to perform on another thread
ThreadStart start = delegate() { // ... // This will work as its using the dispatcher
DispatcherOperation op = Dispatcher.BeginInvoke( DispatcherPriority.Normal, new Action<string>(SetStatus),
"From Other Thread (Async)");
DispatcherOperationStatus status = op.Status; while (status != DispatcherOperationStatus.Completed) { status = op.Wait(TimeSpan.FromMilliseconds(1000));
if (status == DispatcherOperationStatus.Aborted)
{ // Alert Someone } } }; // Create the thread and kick it started! new
Thread(start).Start();
More Examples at:
http://msdn.microsoft.com/en-us/magazine/cc163328.aspx
If you are calling sleep within your application it will do just that. If you do not want your application to hang, you will need to create a new thread that does whatever monitoring and waiting you want it to do, if a particular condition you are looking for is met, then use a callback to your parent thread to perform whatever task you want to do.

Control.Invoke() hangs application

I'm showing an animation while my control is loading the data. When the thread finishes, I hide the animation and show the control. So I'm executing this code from a thread:
protected void InvokeEnableBackControl()
{
if (this.InvokeRequired)
{
this.Invoke(new OpHandler(EnableBackControl));
}
else
{
EnableBackControl();
}
}
Sometimes, when I execute this code, the main thread gets hanged in the following code:
protected virtual void EnableBackControl()
{
if (overlayAnimation.TargetControl != null)
{
overlayAnimation.TargetControl.BringToFront();
}
overlayAnimation.SendToBack();
overlayAnimation.Enabled = false;
overlayAnimation.Visible = false;
}
I'm not sure if it's hanged setting the Enable or Visible property. Do you know any circumstance that may hand the application calling these properties from a Control.Invoke?
Note that Control.Invoke is synchronous, so it will wait for EnableBackControl() to return. Consider using Control.BeginInvoke, which you can "fire and forget."
See this answer: What's the difference between Invoke() and BeginInvoke()
I've run into problems before when I'm executing .Invoke on a background thread while my main thread is still busy - this gives the impression that the app is hung, because the .Invoke just sits there, waiting for the main thread to respond that it's paying attention. Possible causes:
Your main thread is blocked waiting for something
Your main form currently had a modal dialog up, so it's not listening to new requests
Your main thread is spinning, either continually checking if something is finished or doing new work. In my case, the main thread spent the first minute spinning up background threads in a tight loop, so it wasn't listening for any .Invoke requests from background threads.
When you attach the debugger, pay special attention to what your main control MessagePump thread is doing - I suspect its lack of attention is the cause of your trouble. If you identify that it's a tight loop in your main thread that's not responding, try inserting a .DoEvents in the loop, which will pause execution and force the main thread to empty the message pump and route any outstanding requests.
Run in debug, make app hang and then pause debug in Visual Studio and inspect threads.
What I discovered is that the actual drawing/painting of controls can be quite slow, esp if you have a lot of them and/or use double buffering for smooth refresh. I was using BeginInvoke to update a listview control from data I was receiving from a socket. At times the updates were happening so fast that it was freezing the app up. I solved this by writing everything I received in the sockets async receive to a queue, and then in a seperate thread dequeuing the data and using BeginUpdate and EndUpdate on the listview and doing all the outstanding updates in between. This cut out a ton of the extra redrawing and made the app a lot more responsive.
You have to use BeginInvoke inested Invoke see this Link

Categories