How to change ProgressBar values in another WPF thread [duplicate] - c#

This question already has answers here:
How to put delay before doing an operation in WPF
(2 answers)
Change WPF controls from a non-main thread using Dispatcher.Invoke
(4 answers)
Closed 2 days ago.
I am making a program that reads 1 file and writes it to another file, and the main thing is to make a ProgressBar there that will show progress, but when it starts, it swears that ProgressBar and the charCount variable that reads how many characters in a text file are in another stream. I used Dispatcher.Invoke(() => and the program worked but now it hangs until this thread finishes its job.
Thread thread = new Thread(
() =>
{
Dispatcher.Invoke(() =>
{
while (progBar.Value < charCount)
{
progBar.Value++;
Thread.Sleep(100);
}
});
});
thread.Start();
(progBar: ProgressBar name, charCount: stores the number of characters in a text file).
here is a piece of that code. It's strange that the same code without Dispatcher worked in Windows Forms and changed the value of the ProgressBar without hanging. Here it either freezes if you put Sleep(), or instantly changes the value of ProgressBar without Sleep().
I need that while the program is running, the second thread that I create has the ProgressBar value so that the bar fills up without braking the main program.
UPD: I have to use the Thread method.
Here is what the code should look like
Thread thread = new Thread(
() =>
{
while (progBar.Value < charCount)
{
progBar.Value++;
Thread.Sleep(100);
}
});
thread.Start();

Move the Dispatcher call into the loop body. Otherwise the loop - including the Thread.Sleep call - is executed in the UI thread, which will block all UI updates.
here is a sample code to demonstrate how to read from 1 file and write to another file with a progress presentation. you can change it based on your need
Thread thread = new Thread(() =>
{
using (StreamReader reader = new StreamReader("input.txt"))
using (StreamWriter writer = new StreamWriter("output.txt"))
{
int charCount = (int)reader.BaseStream.Length;
int bytesRead = 0;
char[] buffer = new char[4096];
int read;
while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
{
writer.Write(buffer, 0, read);
bytesRead += read;
if (updateCounter++ % 100 == 0)
{
int progress = (int)((double)bytesRead / charCount * 100);
Dispatcher.BeginInvoke(new Action(() =>
{
progBar.Value = progress;
}));
}
}
}
});
thread.Start();

Related

How to make a simple Progress bar timer in Visual C#

I need to have a progress bar drain from full to empty in 5 seconds and i want to be able to interact with other parts of the form application as well. How do I make this happen?
I wrote this block where the program creates a new thread and iterates through it. Each iteration the thread drains a certain amount from it with 'timeToComplete' and finally waits a second.
private void CountDown()
{
int timeToComplete = 5; //Time in seconds
Thread thread = new Thread(() => {
while (progressBar1.Value >= 0)
{
progressBar1.Value -= progressBar1.Maximum / timeToComplete;
Thread.Sleep(1000); //Time is 1 second
}
});
thread.Start();
}

WPF UI Freezes - UI Thread conflict?

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

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.

Thread runs slow when Invoke UI-Element

i am programming a benchmark tool, that reads a bunch of variables from a local server in a thread.
int countReads = 1000;
Int64 count = 0;
for (int i = 0; i < countReads; i++)
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
DateTime start = DateTime.Now;
session.Read(null, 0, TimestampsToReturn.Neither, idCollection, out ReadResults, out diagnosticInfos);
DateTime stop = DateTime.Now;
Thread.CurrentThread.Priority = ThreadPriority.Normal;
TimeSpan delay = (stop - start);
double s = delay.TotalMilliseconds;
count += (Int64)s;
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
progressBar1.Value = i;
}));
}
double avg = (double)count / countReads;
Dispatcher.Invoke(DispatcherPriority.Input, new Action(() =>
{
listBox1.Items.Add(avg);
}));
I am calculating the timespan it took to proceed the read and getting the average timespan at the end.
DateTime start = DateTime.Now;
session.Read(null, 0, TimestampsToReturn.Neither, idCollection, out ReadResults, out diagnosticInfos);
DateTime stop = DateTime.Now
if i run the code without updating the progressbar it took about 5ms average.
but if i run it with
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>
{
progressBar1.Value = i;
}));
it takes about 10 ms average.
My question is, why is the timespan higher when using the progressbar?
i am just calculating the timespan for the read. Not including the progressbar update.
Is there any way to evacuate the ui-painting so that it doesn´t effect my read-timespan?
Thanks for your help.
Best regards
Stop using Invoke to transfer progress information to the UI thread. Publish the progress information to a shared data structure or variable and have the UI thread poll for it using a timer on a reasonable interval. I know it seems like we have all been brainwashed into thinking Invoke is the be-all method for doing worker-to-UI thread interactions, but for simple progress information it can be (and often is) the worst method.
A polling method using a timer on the UI thread offers the following benefits.
It breaks the tight coupling that Invoke imposes on both the UI and worker threads.
The UI thread gets to dictate when and how often it should update the progress information instead of the other way around. When you stop and think about it this is how it should be anyway.
You get more throughput on both the UI and worker threads.
I know this does not directly answer your question as to why session.Read appears to run slower. Try changing your strategy for updating progress information from a push model (via Invoke) to a pull model (via a timer). See if that makes a difference. Even if it does not I would still stick with the pull model for the reasons listed above.
Here is what MSDN says about Dispatcher.Invoke
Executes the specified delegate synchronously on the thread the Dispatcher is associated with.
So, basically, Dispatcher.Invoke blocks until the dispatcher thread as handled the request.
Try Dispatcher.BeginInvoke instead.
If current executing thread is associated with Dispatcher you are using - Invoke() will block this thread so in this case try out using Dispatcher.BeginInvoke() it will do the job asynchronously.
MSDN, Dispatcher.Invoke Method:
Invoke is a synchronous operation; therefore, control will not return
to the calling object until after the callback returns.
BTW, just of interest try out DispatcherPriority.Send
I came 9 years late to the party, but I think this is an even easier solution: Just wait until the progress bar value reaches a certain threshold before updating it. In my example, I refresh the toolbar every fifth of the maximum value.
private static int progressBarMaxValue = -1;
private static int progressBarChunkSize = -1;
public static void progressBarSetNotRealTimeValue(ProgressBar progressBar, int argNewValue)
{
if (progressBarMaxValue != -1)
{
if (argNewValue < progressBarChunkSize)
{
//Threshold not reached yet, discard the new value.
return;
}
else
{
//Allow the update, and set the next threshold higher.
progressBarChunkSize += progressBarChunkSize;
}
}
if (Thread.CurrentThread.IsBackground)
{
progressBar.BeginInvoke(new Action(() =>
{
if (progressBarMaxValue == -1)
{
progressBarMaxValue = progressBar.Maximum;
progressBarChunkSize = progressBar.Maximum / 5;
}
progressBar.Value = argNewValue;
}));
}
else
{
progressBar.Value = argNewValue;
}
}

WPF Progress bar not showing correct progress

I have an application where I am uploading a file in blocks. My front end is WPF and I have a progress bar to show file upload progress (upload is done by separate thread, and the progress bar is in a separate form invoked by the child thread when uploading starts).
I found the total number of blocks in the file to set the maximum property of the progress bar.
Now for each block uploaded I increment the value of progress bar by 1.
But to my surprise, the progress bar starts to increment but never completes ( it stops showing progress after few blocks ).
Here is code for the thread responsible for uploading files:
System.Threading.Thread thread = new Thread(
new ThreadStart(
delegate()
{
// show progress bar - Progress is the name of window containing progress bar
Progress win = new Progress();
win.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
win.Show();
// find number of blocks
long BlockSize = 4096;
FileInfo fileInf = new FileInfo(filename);
long FileSize = fileInf.Length;
long NumBlocks = FileSize / BlockSize;
//set the min and max for progress bar
win.Dispatcher.Invoke(
new Action(
delegate()
{
win.progressBar1.Minimum = 0;
win.progressBar1.Maximum = NumBlocks;
}
), System.Windows.Threading.DispatcherPriority.Render);
//upload file
while (true)
{
// code to upload the file
win.Dispatcher.Invoke(
new Action(
delegate()
{
win.progressBar1.Value += 1;
}
), System.Windows.Threading.DispatcherPriority.Render);
}
}
Can someone help me analyze why is this happening.
Thanks.
Here's the issue:
upload is done by separate thread, and
the progress bar is in a separate form
invoked by the child thread when
uploading starts
If that means your child thread created the form, then that's the problem. Your child thread might be updating the progress bar values, but this will just invalidate the display, and not necessarily refresh the display. When a control's display is invalidated, it simply records that it must redraw it's display the next time it gets a chance. A refresh is when the control actually gets to render to the screen.
A better approach is to create the progress bar form in the main thread.
Your worker thread can then update the status, and your main thread will refresh the display.
One thing to remember: if you're updating a control that was created in a different thread, you must do so via the control's dispatcher.
var dispatcher = progressBar.Dispatcher;
dispatcher.BeginInvoke(new Action( () => { progressBar.Value = currentProgress }));
Edit after seeing the code
All you need to do is move the creation of the progress variable so that it is instantiated by the main thread before the worker thread is created.
Progress win = new Progress();
win.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
win.Show();
System.Threading.Thread thread = new Thread(
new ThreadStart(
delegate()
{
// ...

Categories

Resources