Why UI is freeze and not display one by one? - c#

I have a button, clicking it will processing all files. I want to display the progress when running it. So if file 1 is processing, then the UI displays
"processing file 1"
; when processing file 2, the UI displays
"processing file 1"
"processing file 2"
So I use a Listbox to do it. The ItemSource of the ListBox is a collection in my ViewModel
private ObservableCollection<string> _displayedFiles;
public ObservableCollection<string> DisplayedFiles
{
get {return _displayedFiles;}
set
{
_displayedFiles = value;
PropertyChanged(nameof(DisplayedFiles));
}
}
Now the ViewModel is passed to the command class
public class MyCommand :ICommand
{
private MyViewModel myViewModel;
public MyCommand(MyViewModel myViewModel)
{
this.myViewModel = myViewModel;
}
public void Execute(object parameter)
{
foreach(var f in files)
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate () { AddFiles(f);});
}
}
private void AddFiles(string f)
{
this.ViewModel.DisplayedFiles.Add(f);
}
}
However I found the UI is freeze and the list is not displayed one by one. It displays the whole bunch list together after the loop iteration completed.

When you use Dispatcher.Invoke - you are basically telling the GUI thread to do something.
Since you are doing heavy lifting in that thread the GUI will freeze.
For the progress bar things to work, you always do the time taking intensive task on a different thread and only do progress bar GUI updates on GUI thread.
Let me show you a basic psuedo example:
Imagine my code is:
private void DoTheTaskAndUpdateGUI(List<string> filesToProcess)
{
int completionPct = 0;
for(int idx = 0;idx<filesToProcess.Count; idx++)
{
ProcessFile(filesToProcess[idx]);
completionPct = ((idx + 1)/filesToProcess.Count) * 100;
UpdateGUI(completionPct);
}
}
If all of this is running on single GUI thread, my UI will always be frozen until the loop finishes. To make UI a bit responsive, I can rearrange the fucntionality like:
private void DoTheTaskAndUpdateGUI(List<string> filesToProcess)
{
var guiDispatcher = Dispatcher.CurrentDispatcher;
Task.Factory.StartNew(()=>
{
int completionPct = 0;
for(int idx = 0;idx<filesToProcess.Count; idx++)
{
ProcessFile(filesToProcess[idx]);
completionPct = ((idx + 1)/filesToProcess.Count) * 100;
guiDispatcher.Invoke(UpdateGUI(completionPct));
}
});
}
I have written this code in notepad so don't assume it compiles successfully. but understand the thought behind it.
DoTheTaskAndUpdateGUI method is called on GUI Thread
We hold the Dispatcher instance
A new Thread/Task starts and the heavy lifting
begins in it
GUI Thread is now free
Whenever the intensive task
reaches Dispatcher.Invoke call it updates GUI by requesting GUI
thread to do so

Related

Status Listbox update problem using a Background Worker in C#

I'm trying to use a background worker to update a listbox used for a status window in my Form in C#. It doesn't appear to work properly when the addToStausLog() method is called from another class outside of the MyForm class even though I pass an instance of the form to the other class that's calling the addToStatusLog update member. Instead the update doesn't happen until the class member finished and returns back to the MyForm class. Maybe there's a better a approach to creating real-time status windows that will run from any class that MyForm is passed into. I'm new to worker threads, so could someone review and let me know what I might be doing wrong or could improve on.
public MyForm()
{
InitializeComponent();
// Setup background task to update listbox status so UI is unaffected
_lListBoxQue = new List<string>();
bw_listBoxBGWorker = new BackgroundWorker();
bw_listBoxBGWorker.DoWork += (o, args) => LstbxThread_doWork();
bw_listBoxBGWorker.RunWorkerCompleted += (o, args) => LstbxThread_completed();
}
private void LstbxThread_doWork()
{
System.Threading.Thread.Sleep(100);
}
private void LstbxThread_completed()
{
// Update listbox
lstStatusBox.BeginUpdate();
lstStatusBox.Items.Clear(); // clear entries
lstStatusBox.Items.AddRange(_lListBoxQue.ToArray());
lstStatusBox.EndUpdate();
}
public String addToStatusLog(String sMsg)
{
_lListBoxQue.Add(sMsg);
if (_lListBoxQue.Count > _iStatusLogMaxLines) // > max?
_lListBoxQue.RemoveAt(0); // remove top element?
if( !bw_listBoxBGWorker.IsBusy ) // background not busy?
bw_listBoxBGWorker.RunWorkerAsync(); // update listbox in back ground task
System.Threading.Thread.Sleep(100);
return sMsg;
}
This is the member that calls another class which attempts to call the addToStatusLog several times during the process, but the updates to the listbox don't happen until the MyClass(this).updateDB() finishes. I need to see real-time updates as the updateDB() function is running. There has to be a way to make this work, I'm hoping...
private void btnUpdateDB_Click(object sender, EventArgs e)
{
if (_bIsUpdateEventRunning == false ) // is event not busy?
{
_bIsUpdateEventRunning = true;
new MyClass(this).updateDB();
_bIsUpdateEventRunning = false;
}
}
Example of class called to update the form listbox.
Public class MyClass{
private MyForm _pForm;
public MyClass(MyForm pForm){ _pForm= pForm; }
public void updateDB(){
_pForm.addToStatusLog("Hello World");
}
}
Updated Fix w/o background worker:
public String addToStatusLog(String sMsg)
{
_lListBoxQue.Add(sMsg);
if (_lListBoxQue.Count > _iStatusLogMaxLines) // > max?
_lListBoxQue.RemoveAt(0); // remove top element?
lstStatusBox.BeginUpdate();
lstStatusBox.Items.Clear(); // clear entries
lstStatusBox.Items.AddRange(_lListBoxQue.ToArray());
lstStatusBox.EndUpdate();
Application.DoEvents();
return sMsg;
}
Thread.Sleep is not the answer here. What you likely need is Application.DoEvents. This processes all messages currently waiting in the Windows message queue.
Thread.Sleep just tells the thread to go to sleep for the number of milliseconds you specify. If your background worker is running on the UI thread, you're putting the UI thread to sleep and it's effectively comatose. (Important: All Windows forms run on the UI thread.)
There are, of course, alternative designs that involve spinning up separate threads of execution. But these have their own issues, and you should be mindful of them before running blindly down that path.

Update ObservableCollection in list box in thread

Hy,
I have a Observable Collection which is bind with a list box. I add logs to the Observable Collection. I always add the message immediately to the Observable Collecten. But the list gets only updated when the loop is finished but I want to Update it when I add one item in the for loop. This is why I use a Thread but I have a few problems.
I have a thread safe ObservableCollection:
class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
if (dispatcherObject != null)
{
Dispatcher dispatcher = dispatcherObject.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => handler.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
handler.Invoke(this, e);
}
}
}
This is my test class:
public partial class MainWindow : Window
{
ThreadSafeObservableCollection<Animal> list = new ThreadSafeObservableCollection<Animal>();
public MainWindow()
{
InitializeComponent();
list.Add(new Animal() { Name = "test1" });
list.Add(new Animal() { Name = "test2" });
this.DataContext = list;
}
private void dsofsdkfd(object sender, RoutedEventArgs e)
{
//Version 1
Task.Factory.StartNew(() => test());
//Version2
/*
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var token = Task.Factory.CancellationToken;
Task.Factory.StartNew(() => test(), token, TaskCreationOptions.None, uiScheduler);
*/
}
public void test()
{
for (int i = 0; i < 10000; i++)
{
list.Add(new Animal() { Name = "test" + i });
System.Threading.Thread.Sleep(1);
}
}
}
See the private void dsofsdkfd(object sender, RoutedEventArgs e) function to the comment Version1.
In the beginning it works so the list updates everytime I add a item. After a few entries I get an exception:
"Information for developers (use Text Visualizer to read
this):\r\nThis exception was thrown because the generator for control
'System.Windows.Controls.ListBox Items.Count:1089' with name 'Logger'
has received sequence of CollectionChanged events that do not agree
with the current state of the Items collection. The following
differences were detected:\r\n Accumulated count 994 is different
from actual count 1089. [Accumulated count is (Count at last Reset +
Adds - #Removes since last Reset).]\r\n\r\nOne or more of the following sources may have raised the wrong events:\r\n
System.Windows.Controls.ItemContainerGenerator\r\n
System.Windows.Controls.ItemCollection\r\n
System.Windows.Data.ListCollectionView\r\n *
WpfApplication1.ThreadSafeObservableCollection`1[[WpfApplication1.Animal,
WpfApplication1, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]\r\n(The starred sources are considered more
likely to be the cause of the problem.)\r\n\r\nThe most common causes
are (a) changing the collection or its Count without raising a
corresponding event, and (b) raising an event with an incorrect index
or item parameter.\r\n\r\nThe exception's stack trace describes how
the inconsistencies were detected, not how they occurred. To get a
more timely exception, set the attached property
'PresentationTraceSources.TraceLevel' on the generator to value 'High'
and rerun the scenario. One way to do this is to run a command
similar to the following:\n
System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator,
System.Diagnostics.PresentationTraceLevel.High)\r\nfrom the Immediate
window. This causes the detection logic to run after every
CollectionChanged event, so it will slow down the application.\r\n"
See private void dsofsdkfd(object sender, RoutedEventArgs e) function to the comment Version2.
I also tried it with the TaskScheduler using FromCurrentSynchronizationContext.
Then it throws no exception but I have the same problem like at the beginning, so the list box refreshes only if the for each loop is finished.
How I can accomplish that the list box updates when I add an element?
Best regards
I wouldn't roll my own ObservableCollection for this. I'd just perform the .Add call on the UI thread.
public void test()
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal {Name = "test" + i};
// invoke list.Add on the UI thread
this.Dispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
Note that since you're in a Window subclass, this.Dispatcher will correspond to the dispatcher for the UI thread. If you move this logic to, say, a model or view model class, you'll need to explicitly capture the value of Dispatcher.Current on the UI thread, and pass that dispatcher manually to the background thread.
EDIT: OP asked for more information on using the Dispatcher outside of a FrameworkElement class. Here's how you would do that. The dispatcher for the UI thread is acquired on the UI thread by calling Dispatcher.CurrentDispatcher. That dispatcher is then passed directly into the background thread procedure.
public class MainWindowViewModel
{
// this should be called on the UI thread
public void Start()
{
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
// start the background thread and pass it the UI thread dispatcher
Task.Factory.StartNew(() => BackgroundThreadProc(uiDispatcher));
}
// this is called on the background thread
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal { Name = "test" + i };
// invoke list.Add on the UI thread
uiDispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
}
You need to maintain current dispatcher thread for the same. You must update collection in current dispatcher thread only. One way to do it is to use BiginInvoke() method of dispatcher class.
Save current dispatcher in a variable in constructor and then use it when needed.
_currentDispatcher = Application.Current.Dispatcher;
For example: We have a scenario where we popup an error window if we have an error. We need to close an Error window if error count is zero. Now if we are handling events and message in another thread (not on UI thread) then we need to save the UI thread dispatcher object and need to use it to update collection or any other action. Here I am closing Error Window. (I don't have solution ready for updating collection.)
if (ErrorNotifications.Count == 0)
_currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ErrorNotificationWindow>(CloseErrorNotificationWindow), _errWindow);
Here CloseErrorNotificationWindow is method with parameter _errWindow.
private void CloseErrorNotificationWindow(ErrorNotificationWindow _errWindow)
{
if (_errWindow == null)
return;
if (_errWindow.IsActive)
_errWindow.Close();
}
In CloseErrorNotificationWindow() method you can update your collections and it should not give any exception as you would be using main UI thread to do it.
Hope this will helpful.

Dedicated C# class to update progressbar

I would like to make a dedicated class to update the progress bar in my apps (in this case a WPF progressbar). I did something like this :
public class ProgressBarUpdate : IDisposable
{
private readonly double _delta;
private int _current;
private int _total;
private readonly ProgressBar _pb;
public ProgressBarUpdate(ProgressBar pb, int total)
{
_pb = pb;
_total = total;
// the pb.Maximum is a double so it doesn`t get truncated
_delta = _pb.Maximum / total;
_current = 0;
_pb.Visibility = Visibility.Visible;
}
public void Dispose()
{
_pb.Visibility = Visibility.Collapsed;
_current = 0;
}
public void UpdateProgress()
{
_pb.Value =(int)_delta * (++_current);
}
That i use like this (in the UI thread) :
using (var pu = new ProgressBarUpdate(pb, totalCount)
{
for (x=0; x<totalCount; x++)
{
// operations here
pu.UpdateProgress()
}
}
But the UI, probably blocked, is not updating correctly. What is the best way to display all the progress?
Winforms/WPF program is an Eventing system. There is a single thread which continuously processes events from an event queue. That is its main job and ideally that is the only thing which it should do. Any sort of UI activity generates events in the event queue - like you move your mouse over the window or click something or some other window overlaps your window and then again when it goes away from the overlapped position. All these events are processed by the UI thread and that keeps the UI updated all the time.
Further, Winforms/WPF make it necessary to access and/or update controls and their properties in a thread safe manner by allowing it only on the UI thread.
If you block this UI thread or do some other CPU bound calculation on it, then your UI responsiveness and updated behavior will suffer. Worst case UI will freeze.
Hence the correct answer for you is to do your calculation loop on another worker thread and only update the progress bar UI by marshaling the call to UI thread using the Dispatcher.
However, to answer your question and satisfy your inquisition, here is something that is possible - but it is bad practice and your should never do the following...:
To make it simple, when you update the Value property of the progress bar, it invalidates the progress bar UI - so, UI must update. Hence lets say an event is generated in the event queue which will cause some code to run which will update the UI. However, you are running in a loop over the UI thread - so, the thread has no chance to process this event unless your loop is over. Hence you don't see any UI update. The trick is to make the UI thread process that event before you make the next update on the Value of progress bar. You can do this by forcefully invoking a lower priority item into the event queue - so that normal and higher priority items are processed before going to the next iteration.
using (var pu = new ProgressBarUpdate(pb, totalCount))
{
for (int x = 0; x < totalCount ; x++)
{
// operations here
pu.UpdateProgress();
Dispatcher.Invoke(DispatcherPriority.Background, new Action(()=>{}));
}
}
If you're doing your work, and calling UpdateProgress, on the UI thread then it won't update until you finish the work and the UI thread can do other work (like refresh the UI). So this will never work.
If you're doing your work on a background thread, then you need to use a Dispatcher to marshal the setting the value to the UI thread.
Here's an example from http://tech.pro/tutorial/800/working-with-the-wpf-dispatcher
if (!myCheckBox.Dispatcher.CheckAccess())
{
myCheckBox.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(
delegate()
{
myCheckBox.IsChecked = true;
}
));
}
else
{
myCheckBox.IsChecked = true;
}
Try this:
public ProgressBarUpdate(ProgressBar pb, int total)
{
_pb = pb;
_total = total;
_delta = _pb.MaxValue/((double)total); /make sure you do not truncate delta
_current = 0;
_pb.Visibility = Visibility.Visible;
}
public void Dispose()
{
_pb.Visibility = Visibility.Collapsed;
_current = 0;
}
public void UpdateProgress()
{
_pb.Value = (int)( _delta * (++_current)); //update after the increment
}
I suggest also using float instead of double.
You've been saying you want to avoid using threads, I assume because you don't want unnecessary complication, but it's really not a big deal. It's a very simple matter to make an operation multi-threaded. Even for very short and simple tasks, this is the most straightforward way to achieve what you want. Using TPL, it would look something like this:
using System.Threading.Tasks;
...
Task.Factory.StartNew(() => {
for (...) {
// operation...
progressBar.Dispatcher.BeginInvoke(() => progressBar.Value = ...);
}
});

How to Update ObservableCollection with a new thread and access its items from another UserControl?

I'm working on a project about PDF rendering in the C# language. I convert each page of PDF file to Image and Adds it to a ObservableCollection with a new thread by the below code:
ThreadStart myThreadDelegate = new ThreadStart(DoWork);
myThread = new Thread(myThreadDelegate);
myThread.SetApartmentState(ApartmentState.STA);
void DoWork()
{
for (int i = 0; i < pdfFile.Pages.Count; i++)
{
PdfPage page=pdfFile.LoadPage(i);
myObservableCollection[i]=page;
}
}
then pass the custom item of myObservableCollection to another UserControl for render it but I got an exception:
The calling thread cannot access this object because a different
thread owns it.
I know if I use UI thread my problem could be solved but I want load pdf pages in the background and user doesn't wait for loading all pages and this is possible with a new thread.
You can use threads but have to use the Dispatcher to access UI elements. Only the part, where you pass the item to the UserControl has to be done by the dispatcher.
Application.Current.Dispatcher.BeginInvoke(new Action(() => AddItem()));
BeginInvoke is a asynchronous call and won't block the execution of the following code.
Edit: I'm still not 100% sure if I unterstood the whole idea of your application but made a small sample which demonstrates how you can use threads and UI elements.
I made a Window (that would be your UserControl) which contains a Button and a ListBox. When clicking the Button a thread is started and processes some items. In my case it just adds some texts into a list, I added Thread.Sleep(1000) to simulate the processing of lots of stuff. When the text is prepared, it will be added to the ObservableCollection, which has to be done by the UI thread (Dispatcher). There is nothing blocking the UI but this adding and this is done very fast. You can also start multiple threads at the same time.
This is the code-behind of the Window (the Window itsself just contains a Button and a ListBox):
public partial class MainWindow : Window
{
private ObservableCollection<string> textList;
public MainWindow()
{
textList = new ObservableCollection<string>();
InitializeComponent();
btnStartWork.Click += BtnStartWorkClick;
lstTextList.ItemsSource = textList;
}
private void BtnStartWorkClick(object sender, RoutedEventArgs e)
{
Thread myThread;
ThreadStart myThreadDelegate = DoWork;
myThread = new Thread(myThreadDelegate);
myThread.SetApartmentState(ApartmentState.STA);
myThread.Start();
}
private void DoWork()
{
for (int i = 0; i < 5; i++)
{
string text = string.Format("Text {0}", i);
// block the thread (but not the UI)
Thread.Sleep(1000);
// use the dispatcher to add the item to the list, which will block the UI, but just for a very short time
Application.Current.Dispatcher.BeginInvoke(new Action(() => textList.Add(text)));
}
}
}

control.invoke() issue

Im trying to update a progress bar while doing some data type checks on a separate thread and there seems to be a delay between what value the progress bar is at and the value which is actually show.
The following code is executed by the non-GUI thread and is used to raise the event.
protected virtual void OnUpdateProgressBar(object sender, ProgressBarEventArgs e)
{
EventHandler<ProgressBarEventArgs> TempHandler = UpdateProgressBar;
//Avoid possible race condition.
if (TempHandler != null)
{
TempHandler(this, e);
}
}
I have created a separate class for updating the progress bar and when i create an instance of it, i pass a reference to the progress bar. Below is the entire class.
public class ProgressBarChanged
{
ProgressBar statusBar;
public ProgressBarChanged(ProgressBar pb)
{
statusBar = pb;
statusBar.Value = 0;
}
public ProgressBarChanged()
{
}
public void subscribeToEvent(DataVerification test)
{
test.UpdateProgressBar += new EventHandler<ProgressBarEventArgs>(incrementPB);
}
public void incrementPB(object sender, ProgressBarEventArgs e)
{
Action action = () =>
{
if (e.CurrentRow == e.FinalRow - 10)
{
int i = 5;
}
statusBar.Maximum = e.FinalRow;
statusBar.Value = e.CurrentRow;
};
if(statusBar.InvokeRequired)
statusBar.Invoke(action);
else
action();
}
}
I have uploaded a screen shot showing the progress bar and the actual values.
Any ideas???
Thanks
The progessbar is a simple feedback to the user, not a piece of exact instrumentation. It's a pacifier.
It also incorporates it's own async logic to update the screen (independent of the message loop). This makes that it may run a little behind.
What's the big deal?
To get more accurate results, divide your range into < 100 segments and do fewer updates.
A delay is pretty normal. After all, invoking a method in the UI thread means Windows will dispatch a message and if your thread is fast enough (and CPU consuming) then it'll appear faster than UI.

Categories

Resources