WPF UI Freezes - UI Thread conflict? - c#

I'm trying to create an image slideshow effect with WPF.
The method to update the slideshow with a new image is called every few seconds in a Windows.Forms.Timer, and runs in it's own thread within a Task (as seen below).
private void LoadImage()
{
Task t = Task.Run(() =>
{
this.Dispatcher.BeginInvoke((Action)(() =>
{
TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);
Fader.ChangeSource(image, BitmapFromUri(new Uri(compPath + oComps[nCount].name)), delay, delay);
image.Visibility = System.Windows.Visibility.Visible;
mediaElement.Stop();
mediaElement.Close(); ;
mediaElement2.Stop();
mediaElement2.Close();
mediaElement.Visibility = System.Windows.Visibility.Collapsed;
mediaElement2.Visibility = System.Windows.Visibility.Collapsed;
imageLoop.Interval = oComps[nCount].duration;
nCount++;
imageLoop.Start();
}));
});
}
Simultaneously, there is a scrolling text banner across the bottom of the canvas in an overlay. This too is running in it's own thread, updating the UI through a Dispatcher.
Every few images, both the scrolling text and the slideshow will pause for a second or two, seemingly waiting for the image to load. This behaviour is unexpected as each element is in a seperate thread.
Could this be a conflict between the two Task threads updating the UI thread?
What could be causing this?

Your code to put work on another thread does not put the work on another thread. Your BeginInvoke is sending it back to the UI thread and all your work is being done there.
Do the heavy work before you do the BeginInvoke call so the work actually happens on the background thread.
private void LoadImage()
{
Task t = Task.Run(() =>
{
//I assume BitmapFromUri is the slow step.
var bitmap = BitmapFromUri(new Uri(compPath + oComps[nCount].name);
//Now that we have our bitmap, now go to the main thread.
this.Dispatcher.BeginInvoke((Action)(() =>
{
TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);
//I assume Fader is a control and must be on the UI thread, if not then move that out of the BeginInvoke too.
Fader.ChangeSource(image, bitmap), delay, delay);
image.Visibility = System.Windows.Visibility.Visible;
mediaElement.Stop();
mediaElement.Close(); ;
mediaElement2.Stop();
mediaElement2.Close();
mediaElement.Visibility = System.Windows.Visibility.Collapsed;
mediaElement2.Visibility = System.Windows.Visibility.Collapsed;
imageLoop.Interval = oComps[nCount].duration;
nCount++;
imageLoop.Start();
}));
});
I suspect your banner is also not actually doing work on the other thread, you may want to look in to it.
A even better solultion if possible is rewrite BitmapFromUri to be async and not use threads at all.
private async Task LoadImageAsync()
{
TimeSpan delay = new TimeSpan(0, 0, 0, 0, 0);
var bitmap = await BitmapFromUriAsync(new Uri(compPath + oComps[nCount].name);
Fader.ChangeSource(image, bitmap), delay, delay);
image.Visibility = System.Windows.Visibility.Visible;
mediaElement.Stop();
mediaElement.Close(); ;
mediaElement2.Stop();
mediaElement2.Close();
mediaElement.Visibility = System.Windows.Visibility.Collapsed;
}

Related

The UI thread freezes when trying to update a control with Application.Current.Dispatcher.BeginInvoke

I've looked at dozens of different questions related to this issue and everyone seems to recommend what I'm already doing. I'm trying to figure what I'm doing wrong.
Here's the code, it's really simple:
new Thread(() =>
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
for (int i = 0; i < 50000; i++)
{
Rectangle rectangle = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5, 0, 5, 5)
};
Others.Children.Add(rectangle);
}
}));
}).Start();
Others is a WrapPanel.
<WrapPanel Name="Others" Orientation="Horizontal" />
based on the other threads I've seen, the UI thread should remain responsive as the rectangles are being created and added to the WrapPanel. But it doesn't happen. The UI hangs.
Any ideas on what I'm doing wrong?
After some testing, I got your above scenario working without blocking the UI.
Note that I was able to achieve this using async/await and Tasks. This allows the adding of children to be put into the ThreadPool of the application, allowing work to be executed once a thread is available. During this time, the message pumping still occurs, causing the UI thread not to block.
If you wrap your WrapPanel in a ScrollViewer, you can see that you are still able to scroll while new Rectangles are being added.
private async void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 50000; i++)
{
var rect = new Rectangle
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5,0,5,5)
};
await Task.Run(async ()=> await AddChildAsync(rect));
}
}
private async Task AddChildAsync(Rectangle rect)
{
await Application.Current.Dispatcher.InvokeAsync(()=> Others.Children.Add(rect));
}
Another approach would be to not use Tasks all together, but instead, allow the Dispatcher to switch control to process events.
private async void OnMainWindowLoaded(object sender, RoutedEventArgs e)
{
for (var i = 0; i < 50000; i++)
{
var rect = new Rectangle
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5,0,5,5)
};
Others.Children.Add(rect);
await System.Windows.Threading.Dispatcher.Yield();
}
}
The new thread isn't really accomplishing anything useful. Because the thread does nothing other than to immediately call BeginInvoke(), all of the real work just winds up back on the dispatcher thread, where it blocks that thread until it's done.
You could, as one comment suggests refactor the loop so that the loop itself is in the thread, while the actual UI operations are not. That might look something like this:
new Thread(() =>
{
for (int i = 0; i < 50000; i++)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
Rectangle rectangle = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Gray),
Width = 200,
Height = 290,
Margin = new Thickness(5, 0, 5, 5)
};
Others.Children.Add(rectangle);
}));
}
}).Start();
But I'm not sure that's really going to do what you want. It will queue up 50,000 separate invocations on the UI thread, all of which will definitely impede that thread's ability to handle other window messages, if not completely block any other work until they are all complete. The net effect will be very similar, if not identical, to what you're seeing now.
The other issue is that, this is WPF and you are apparently trying to create UI in code-behind.
It's not really clear how successful you're going to be getting 50,000 different Rectangle objects populated without some type of delay perceivable by the user. But you should definitely consider creating a view model type to represent the actual Rectangle objects, store them in an ObservableCollection<T>, or even just a plain List<T>, populate the collection in the background, and let WPF deal with creating the necessary Rectangle objects through a DataTemplate and an appropriate binding.
If you use that approach, using a List<T> you would just create the whole list, and then update a property exposing the list, which would avoid potential cross-thread issues when the collection is updated, as well as the per-rectangle cross-thread invocation cost.

Why using Task.ContinueWith hurts my program's responsiveness?

We have a video player written in WPF with a scroll bar. When the scroll bar is dragged right-left the CurrentFrameTime is updated and triggers UpdateFrames which, in turn, grabs the frame and shows it. That works fine.
But, sometimes grabbing the frame can take time (because of the disk for example) and though CurrentFrameTime value can be already changed, the UpdateFrames can be "stuck" and still be waiting for previous frame in ...GetAsync().Result.
What is decided to do is to move Dispatcher.BeginInvoke into ContinueWith block. Now, each time the CurrentFrameTime is changed, the previous operation will be canceled(we don't need to show the frame if frame time was already changed) and an up-to-date frame should be shown. But, for some reason because of this change the application became slower. When I drag the scroll it can take a few seconds before the image is updated.
What could happened that moving the code into ContinueWith has slowed down the video player?
MainApplication without ContinueWith
_threadUpdateUI = new Thread(new ThreadStart(UpdateFrames));
public long CurrentFrameTime
{
get{...}
set
{
...
_fetchFrame.Set();
}
}
void UpdateFrames()
{
while(run)
{
_fetchFrame.WaitOne();
var frame = Cache.Default.GetAsync(CurrentFrameTime)
.Result;
Dispatcher.BeginInvoke(new Action(() => ShowFrame(frame.Time, frame.Image)));
}
}
Cache
public Task<VideoFrame> GetAsync(long frameTime)
{
//this i used when cache is disabled
if (GrabSynchronously)
{
var tcs = new TaskCompletionSource<VideoFrame>();
//reading from file
var frame2 = FrameProvider.Instance.GetFrame(frameTime);
tcs.SetResult(frame2);
return tcs.Task;
}
...
}
MainApplication WITH ContinueWith
void ShowFrames()
{
while(run)
{
_fetchFrame.WaitOne();
_previousFrameCancellationToken.Cancel();
_previousFrameCancellationToken = new CancellationTokenSource();
Cache.Default.GetAsync(CurrentFrameTime).ContinueWith((task) =>
{
var frameTime = task.Result.Time;
var frameImage = task.Result.Image
Dispatcher.BeginInvoke(new Action(() => ShowFrame(frameTime, frameImage)));
}, _previousFrameCancellationToken.Token);
}
}
df
In your old way your UpdateFrames loop would block every .Result call. This made your loop self metering, only allowing one request "in flight" at a time even if _fetchFrame got .Set() called many times while it was waiting for .Result to finish.
In your new way every call to _fetchFrame.Set() triggers another task to start up and be "in flight" (assuming GrabSynchronously is false) even if it never gets used. This is flooding your system with requests and is causing your slowdown.
One possible solution is to put another semaphore of some type to limit the number of concurrent requests for frames you can handle.
Semaphore _frameIsProcessing = new Semaphore(5, 5); //Allows for up to 5 frames to be requested at once before it starts blocking requests.
private void ShowFrames()
{
while (run)
{
_fetchFrame.WaitOne();
_previousFrameCancellationToken.Cancel();
_previousFrameCancellationToken = new CancellationTokenSource();
_frameIsProcessing.WaitOne();
Cache.Default.GetAsync(CurrentFrameTime).ContinueWith((task) =>
{
_frameIsProcessing.Release();
if(_previousFrameCancellationToken.IsCancellationRequested)
return;
var frameTime = task.Result.Time;
var frameImage = task.Result.Image;
Dispatcher.BeginInvoke(new Action(() => ShowFrame(frameTime, frameImage)));
});
}
}

Xamarin.ios - Timer not executing actions after time

I am currently making a little game in Xamarin.ios and the idea is that you have 30 seconds from the time you press start until its over (and takes you to the game over screen) but if you do the wrong move then its also game over. I am using System.Timers and the timer starts fine and begins ticking but when you get to 30 seconds and it stops, it prints to the console that the timer has finished but then doesn't do anything I want it to. I have tried everything I can think of but can't seem to figure it out. Any help is appreciated, thank you! Also sorry in advance, this is my first time using stackoverflow.
Here is the main code:
//Setting up the timer
int count = 1;
System.Timers.Timer timer1;
private void OnTimeEvent(object source, ElapsedEventArgs e)
{
count++;
Console.WriteLine ("timer tick");
if (count == 30)
{
timer1.Enabled = false;
Console.WriteLine ("timer finished");
//**Everything from here on is just what I want to happen**
imgBackground.Image = UIImage.FromFile ("gameover.jpg");
btn1.SetBackgroundImage (null, UIControlState.Normal);
btn2.SetBackgroundImage (null, UIControlState.Normal);
btn3.SetBackgroundImage (null, UIControlState.Normal);
btn4.SetBackgroundImage (null, UIControlState.Normal);
lblTime.Text = "";
btnStart.Enabled = true;
hasend = true;
//score is set back to 0
score = 0;
btn1green = false;
btn2green = false;
btn3green = false;
btn4green = false;
}
}
Here is what I have on the start button (which works fine)
//Start the timer
timer1.Enabled = true;
Console.WriteLine("timer started");
And here is what I have when you make a wrong move (also works fine)
else
{
//adjust the UI
imgBackground.Image = UIImage.FromFile("gameover.jpg");
btn1.SetBackgroundImage(null, UIControlState.Normal);
btn2.SetBackgroundImage(null, UIControlState.Normal);
btn3.SetBackgroundImage(null, UIControlState.Normal);
btn4.SetBackgroundImage(null, UIControlState.Normal);
lblTime.Text = "";
btnStart.Enabled = true;
hasend = true;
//score is set back to 0
score = 0;
btn1green = false;
btn2green = false;
btn3green = false;
btn4green = false;
timer1.Enabled = false;
Console.WriteLine("timer stopped");
}
So as you can see that is a decent amount to change in the UI once the timer finishes. I think that might have something to do with why it's not working
EDIT: This is also where I set up the timer under ViewDidLoad ()
timer1 = new System.Timers.Timer();
timer1.Interval = 1000;
timer1.Elapsed += new ElapsedEventHandler(OnTimeEvent);
The problem is Timer is executed on a background thread. And background-threads can't modify UI objects.
timer1.Elapsed is set from the UI-thread in ViewDidLoad(). But it will be called from a background thread.
The solution is to wrap the code that affects UI objects in a InvokeOnMainThread (iOS) or RunOnUIThread (Android).
For an example for InvokeOnMainThread: http://developer.xamarin.com/guides/ios/user_interface/controls/part_2_-_working_with_the_ui_thread/
You can only access UI components on the UI thread. OnTimeEvent is being called from the timer's background thread not the UI thread. Another solution rather than using System.Timer and InvokeOnMainThread is to use TPL, which Xamarin supports. This will also work on android and winphone too (I think InvokeOnMainThread is ios specific?)
TaskScheduler uiContext = TaskScheduler.FromCurrentSynchronizationContext();
Console.WriteLine("timer started");
Task.Delay(30000).ContinueWith((task) =>
{
//Do UI stuff
label.Text = "!!";
Console.WriteLine("timer stopped");
}, uiContext);
Task.Delay(30000) starts and returns a background task that takes 30 seconds to complete. The ContinuesWith() call provides a method to run after that task completes, a continuation task. To make sure that continuation task runs on the UI thread a TaskScheduler is passed in. This TaskScheduler was taken from the UI thread.
Even simpler than this because you are using C# you can use the built in language support to write all that shorter:
async Task Run()
{
Console.WriteLine("timer started");
await Task.Delay(30000);
//Do UI stuff
label.Text = "!!";
Console.WriteLine("timer stopped");
}
Then just call Run();
The await keyword effectively automatically sets up a continuation task for the rest of the method and returns immediately. The rest of the method is run after the Task.Delay(30000), but the switch back to the UI context is automatically handled as await captures it.
Considering that you setup your timer in ViewDidLoad() (so you don't have any sharing code benefit in using the .Net timer) you can also use the native Timer writing just one line:
NSTimer timer = NSTimer.CreateScheduledTimer(TimeSpan.FromSeconds(30), delegate { DoYourStuffEveryTimeSpan(); });
In your case since background-threads can't modify UI objects just wrap your UI Changes with BeginInvokeOnMainThread like this:
DoYourStuffEveryTimeSpan()
{
BeginInvokeOnMainThread(() =>
{
DoMagicUIChanges();
});
}
var timer = new Timer(2000);
timer.Elapsed += OnTimerElapsed;
timer.Start ();
Console.WriteLine ("Timer started, control is back here");
void OnTimerElapsed(object sender, EventArgs e)
{
Console.WriteLine ("Time Elapsed");
}

Custom ProgressBar Indicator not showing up before time consuming action

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;
}

Locking and Unlocking WritableBitmap

I'm trying to write into a WritableBitmap and I want to do the data processing in a non-UI thread.
So I'm calling the Lock and Unlock methods from the UI dispatcher and the rest is done on a different thread:
IntPtr pBackBuffer = IntPtr.Zero;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.WriteLine("{1}: Begin Image Update: {0}", DateTime.Now, this.GetHashCode());
_mappedBitmap.Lock();
pBackBuffer = _mappedBitmap.BackBuffer;
}));
// Long processing straight on pBackBuffer...
Application.Current.Dispatcher.Invoke(new Action(()=>
{
Debug.WriteLine("{1}: End Image Update: {0}", DateTime.Now, this.GetHashCode());
// the entire bitmap has changed
_mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
_mappedBitmap.PixelHeight));
// release the back buffer and make it available for display
_mappedBitmap.Unlock();
}));
This code can be called from any thread, since it specifically calls the UI dispatcher when needed.
This works when my control is not under great stress. But when I call this every 100ms almost immediately I get an InvalidOperationException from AddDirtyRect with the following message:
{"Cannot call this method while the image is unlocked."}
I don't understand how this can happen. My Debug Output logs show that Lock indeed has been called for this instance of my class.
UPDATE
My entire scenario: I'm writing a class which will allow diplaying floating-point matrices in a WPF Image control. The class FloatingPointImageSourceAdapter allows setting data using the API
void SetData(float[] data, int width, int height)
And it exposes a ImageSource which an Image control Souce property can be bound to.
Internally this is implemented using WritableBitmap. Whenever a user sets new data I need to process the pixels and rewrite them into the buffer. The data is planned to be set at a high frequency and this is why I went for writing directly into the BackBuffer instead of calling WritePixels. Moreover, since the remapping of the pixels can take a while and the images can be quite large, I want to do the processing on a separate thread.
I have decided to deal with high stress by dropping frames. So I have an AutoResetEvent which keeps track of when the user has requested to update the data. And I have a background task which does the actual work.
class FloatingPointImageSourceAdapter
{
private readonly AutoResetEvent _updateRequired = new AutoResetEvent(false);
public FloatingPointImageSourceAdapter()
{
// all sorts of initializations
Task.Factory.StartNew(UpdateImage, TaskCreationOptions.LongRunning);
}
public void SetData(float[] data, int width, int height)
{
// save the data
_updateRequired.Set();
}
private void UpdateImage()
{
while (true)
{
_updateRequired.WaitOne();
Debug.WriteLine("{1}: Update requested from thread {2}, {0}", DateTime.Now, this.GetHashCode(), Thread.CurrentThread.ManagedThreadId);
IntPtr pBackBuffer = IntPtr.Zero;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.WriteLine("{1}: Begin Image Update: {0}", DateTime.Now, this.GetHashCode());
_mappedBitmap.Lock();
pBackBuffer = _mappedBitmap.BackBuffer;
}));
// The processing of the back buffer
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.WriteLine("{1}: End Image Update: {0}", DateTime.Now, this.GetHashCode());
// the entire bitmap has changed
_mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
_mappedBitmap.PixelHeight));
// release the back buffer and make it available for display
_mappedBitmap.Unlock();
}));
}
}
}
I have dropped a lot of code here for the sake of bravity.
My test creates a task which calls SetData within certain intervals:
private void Button_Click_StartStressTest(object sender, RoutedEventArgs e)
{
var sleepTime = SleepTime;
_cts = new CancellationTokenSource();
var ct = _cts.Token;
for (int i = 0; i < ThreadsNumber; ++i)
{
Task.Factory.StartNew(() =>
{
while (true)
{
if (ct.IsCancellationRequested)
{
break;
}
int width = RandomGenerator.Next(10, 1024);
int height = RandomGenerator.Next(10, 1024);
var r = new Random((int)DateTime.Now.TimeOfDay.TotalMilliseconds);
var data = Enumerable.Range(0, width * height).Select(x => (float)r.NextDouble()).ToArray();
this.BeginInvokeInDispatcherThread(() => FloatingPointImageSource.SetData(data, width, height));
Thread.Sleep(RandomGenerator.Next((int)(sleepTime * 0.9), (int)(sleepTime * 1.1)));
}
}, _cts.Token);
}
}
I run this test with ThreadsNumber=1 and with SleepTime=100 and it crashes with the aforementioned exception.
UPDATE 2
I tried checking that my commands indeed execute serially.
I added another private field
private int _lockCounter;
And I manipulate it in my while loop:
private void UpdateImage()
{
while (true)
{
_updateRequired.WaitOne();
Debug.Assert(_lockCounter == 0);
_lockCounter++;
IntPtr pBackBuffer = IntPtr.Zero;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.Assert(_lockCounter == 1);
++_lockCounter;
_mappedBitmap.Lock();
pBackBuffer = _mappedBitmap.BackBuffer;
}));
Debug.Assert(pBackBuffer != IntPtr.Zero);
Debug.Assert(_lockCounter == 2);
++_lockCounter;
// Process back buffer
Debug.Assert(_lockCounter == 3);
++_lockCounter;
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Debug.Assert(_lockCounter == 4);
++_lockCounter;
// the entire bitmap has changed
_mappedBitmap.AddDirtyRect(new Int32Rect(0, 0, _mappedBitmap.PixelWidth,
_mappedBitmap.PixelHeight));
// release the back buffer and make it available for display
_mappedBitmap.Unlock();
}));
Debug.Assert(_lockCounter == 5);
_lockCounter = 0;
}
}
I was hoping that if the message order was somehow messed up my Debug.Asserts would catch this.
But everything with the counters is fine. They are incremented correctly according to the serial logic, and still I get the exception from AddDirtyRect.
Application.Current.Dispatcher.Invoke will try to execute the method that is passed as delegate to on the UI thread itself, this will happen when UI thread is free. If you try to execute the instructions on this continuously it'll almost nothing like performing the operation on the UI thread. Always the instruction executed on Application.Current.Dispatcher.Invoke should be very minimal say it should be only one line like which shall change only value on the UI nothing more than that. So avoid complex operations that are performed as part Dispatcher, move it out of dispatcher and do the only the operations that updates the UI
So after some (very long) digging, it turned out the real bug was hidden in the code I left out for the sake of bravity :-)
My class allows changing the size of the image. When setting data, I check if the new size is the same as the old size and if it isn't I initialize a new WritableBitmap.
What happened was that the size of the image was changed (using a different thread) sometime in the middle of the while loop. And this caused different stages of the processing code to process different instances of _mappedBitmap (since _mappedBitmap pointed to different instances throughout the different stages). So when the instance was changed to a new one, it was created in an unlocked state, thus causing the (rightful) exception.

Categories

Resources