Cannot get Progress Bar to Update while running tasks in WPF

CodeLiftsleep

Member
Joined
Mar 11, 2016
Messages
20
Programming Experience
3-5
Not sure what I am doing wrong, but I can't get it to work. I am doing a CPU intensive task where I am creating a large number of players for a football game I am making, and I am using the Task.Factory.StartNew method to run it on. I then create a separate task for the Progress updater but I can't seem to get it to work.

I have a WPF progressbar which I have bound to an INotifyPropertyChanged Property, however it does not update and the UI appears frozen, which I thought tasks were supposed to eliminate...

VB.NET:
    TimeIt(Sub() ReallyGenNewPlayers())

    Private Sub TimeIt(MyAction As Action)
        Dim SW As New Stopwatch
        SW.Start()
        MyAction()
        Console.WriteLine($"Total Time Generating Players: {SW.Elapsed} seconds")
        SW.Stop()
    End Sub


    Private Sub ReallyGenNewPlayers()
        Task.Factory.StartNew(Sub() GenNewPlayersASync()).Wait()
    End Sub


     Private Sub GenNewPlayersASync()
        Dim x As Integer = 0
        'Generate the Players on an Async Thread

        For i As Integer = 1 To NumPlayers
            x = i
            Task.Factory.StartNew(Sub() CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()
            Dim mytask As Task(Of Double) = Task(Of Double).Factory.StartNew(Function() UpdateProgressBar(x, NumPlayers))
            ProgBarValue = mytask.Result
        Next i

    End Sub

    Private Function UpdateProgressBar(ByVal playernum As Integer, ByVal TotalPlayers As Integer) As Double
        Dim myval As Double
        Return myval = (playernum / TotalPlayers) * 100
    End Function

Any help would be appreciated, and if you could let me know the how's and why's I would appreciate it as well so I can learn...
 
Any changes you make to a control must be done on the UI thread. Yes, use a background task to perform your long-running operation but then any updates to the UI during that task must be marshalled to the UI thread to be performed. Here's a WPF-specific example I added to a thread specifically on updating the UI from a secondary thread:

Accessing Controls from Worker Threads
 
Any changes you make to a control must be done on the UI thread. Yes, use a background task to perform your long-running operation but then any updates to the UI during that task must be marshalled to the UI thread to be performed. Here's a WPF-specific example I added to a thread specifically on updating the UI from a secondary thread:

Accessing Controls from Worker Threads

Isn't Creating another task for the ProgressBar giving it its own thread? Or is it simply adding another task to the same thread?

Is it possible to do this via a Task instead of a background worker?

Is it not possible to simply do this via a UI Binding to a INotififyPropertyChanged Property without using a background worker or a task?
 
Isn't Creating another task for the ProgressBar giving it its own thread? Or is it simply adding another task to the same thread?
A Task is executed on a thread pool thread. If you create a new Task then you're still executing it on a secondary thread. As I said, any modification of a control must be done specifically on the UI thread. It doesn;t matter which secondary thread you use, a secondary thread is not the UI thread.
Is it possible to do this via a Task instead of a background worker?
Of course. The use of the BackgroundWorker was simply convenient for the example but is irrelevant to the principle. The principle is that if you are executing code on a secondary thread and you want to update the UI then you need to marshal a method call to the UI thread to do that. A Task and a BackgroundWorker are simply two different mechanisms to execute code on a secondary thread but both end up using a thread pool thread so there's not even that difference. Stop looking at the irrelevant differences and focus on the relevant similarities.
Is it not possible to simply do this via a UI Binding to a INotififyPropertyChanged Property without using a background worker or a task?
The problem is that updating a property on a secondary thread is going to raise the corresponding event on that same secondary thread, so if you've bound a control then you're still trying to modify that control on that same secondary thread. At some point, you need to marshal a method call to the UI thread. If you're going to be using multi-threading then you could do that in your ViewModel and use the SynchronizationContext class to ensure that the event is raised on the UI thread. There's an example of using the SynchronizationContext class in that same thread I linked to earlier.
 
A Task is executed on a thread pool thread. If you create a new Task then you're still executing it on a secondary thread. As I said, any modification of a control must be done specifically on the UI thread. It doesn;t matter which secondary thread you use, a secondary thread is not the UI thread.

Of course. The use of the BackgroundWorker was simply convenient for the example but is irrelevant to the principle. The principle is that if you are executing code on a secondary thread and you want to update the UI then you need to marshal a method call to the UI thread to do that. A Task and a BackgroundWorker are simply two different mechanisms to execute code on a secondary thread but both end up using a thread pool thread so there's not even that difference. Stop looking at the irrelevant differences and focus on the relevant similarities.

The problem is that updating a property on a secondary thread is going to raise the corresponding event on that same secondary thread, so if you've bound a control then you're still trying to modify that control on that same secondary thread. At some point, you need to marshal a method call to the UI thread. If you're going to be using multi-threading then you could do that in your ViewModel and use the SynchronizationContext class to ensure that the event is raised on the UI thread. There's an example of using the SynchronizationContext class in that same thread I linked to earlier.

OK gotcha...using a background worker per your example...

One last issue I am having is that I need to get the current value of "x" in the player generation sub to be able to know the value that should be updated, so I would need to call the worker from inside the secondary thread...however inside the BackgroundWorker1_DoWork event, I have no parameters to supply the Me.UpdateProgressBar since I don't know what they are at that point...


There is also no "InvokeRequired" property on a progress bar going by your code that returns a value, so I am unsure what to put there...
VB.NET:
Private Sub GenNewPlayersASync()
        Dim x As Integer = 0
        'Generate the Players on an Async Thread

        For i As Integer = 1 To NumPlayers
            x = i
            Task.Factory.StartNew(Sub() CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()

       ---->'Call Worker UpdateProgressBar(x, NumPlayers)

            
        Next i

    End Sub
 
Last edited:
In WPF, the correct way to do this is to create a view model with a property that has change notification enabled through INotifyPropertyChanged. Your task would update the view model property (let's say PercentDone), and the UI progressbar would be bound to that property. You are not supposed to EVER have to directly interact with the UI in WPF.

XML:
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1" x:Class="MainWindow"
    Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:MyViewModel/>
    </Window.DataContext>

    <Grid>
        <ProgressBar  Minimum="0" Maximum="100" Value="{Binding PercentDone}" />
    </Grid>
</Window>

VB.NET:
Public Class MyViewModel
    Implements INotifyPropertyChanged

    Private _percentDone As Decimal = 0
    Public Property PercentDone As Decimal
        Get
            Return _percentDone
        End Get
        Set(value As Decimal)
            If value > 100 Then value = 0
            _percentDone = value
            NotifyPropertyChanged()
        End Set
    End Property

    Public Sub New()
        Task.Factory.StartNew(Sub() LoopPercent())
    End Sub

    Private Sub LoopPercent()
        While True
            PercentDone += 1
            Thread.Sleep(500)
        End While
    End Sub

#Region "INotifyPropertyChanged"
    Public Sub NotifyPropertyChanged(<CallerMemberName> Optional ByVal propName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
    End Sub

    Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
#End Region

End Class
 
Last edited:
In WPF, the correct way to do this is to create a view model with a property that has change notification enabled through INotifyPropertyChanged. Your task would update the view model property (let's say PercentDone), and the UI progressbar would be bound to that property. You are not supposed to EVER have to directly interact with the UI in WPF.


Yes, I realize that, I actually created a separate window for the Progress Bar, but the issue occurs because I'm still getting the result from inside the secondary thread which blocks it from being able to use it...

It's the first foray into multithreading so I know I am asking a lot of dumb questions, but please bear with me. I typically go through this stage when I'm learning a new concept and after struggling with it for a while I all of a sudden have a light bulb go off in my head and then I quickly figure it out and learn various other ways to do it...

I know I'm frustrating some of the more experienced users here with some of this, and I apologize for it, but it's the way I learn...I ask lot's of questions and then I keep working at it until I get it working properly. Then I go back and review things I could do to make it more efficient or better, and then I end up having a good understanding of it...
 
Look at the example I added... The view model is the ONLY part of code that connects to your XAML. You can have as many threads as you like, as long as the property you defined in your view model is visible from your XAML, it will work.

I will add that you should probably start multithreading without WPF, to really understand how to do it manually. WPF and its awesome databinding hides much of it away. If you were in WinForm, then your issue would matter, and you would need to test your UI controls through .InvokeRequired and then invoke the form to change the control value. In WPF, you just tell the form control where it should go find its value, relative to the data context, and then you just make sure that value is updated. You don't have to push the value into the control, neither should you be trying to. That's the biggest thing to get your head around in WPF. Once you understand that, you get the basics of MVVM. The view is bound to the view model, and the view model exposes data from the model.
 
Last edited:
Look at the example I added... The view model is the ONLY part of code that connects to your XAML. You can have as many threads as you like, as long as the property you defined in your view model is visible from your XAML, it will work.

I will add that you should probably start multithreading without WPF, to really understand how to do it manually. WPF and its awesome databinding hides much of it away. If you were in WinForm, then your issue would matter, and you would need to test your UI controls through .InvokeRequired and then invoke the form to change the control value. In WPF, you just tell the form control where it should go find its value, relative to the data context, and then you just make sure that value is updated. You don't have to push the value into the control, neither should you be trying to. That's the biggest thing to get your head around in WPF. Once you understand that, you get the basics of MVVM. The view is bound to the view model, and the view model exposes data from the model.

Awesome! Thanks so much...

Now my question is going to be how do I get the value out of the secondary thread, because I need the value from there to know what percent the progress bar should display... Here is the Secondary thread where I need to take the value of x and NumPlayers to calculate a percent...ie x is the number created so far out of NumPlayers total... I have to pass this to the ViewModel somehow for it to know the value of the ProgressBar so it can display it properly...Can I just call the ViewModel property directly from there? Or do I need to use Dispatcher.Invoke?




VB.NET:
Private Sub GenNewPlayersASync()
        Dim x As Integer = 0
        'Generate the Players on an Async Thread

        For i As Integer = 1 To NumPlayers
            x = i
            Task.Factory.StartNew(Sub() CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()

       ---->'Call Worker UpdateProgressBar(x, NumPlayers)

            
        Next i

    End Sub

Also....Since this is in a loop, should the sub I'm creating be the entire loop? Otherwise am I not wasting significant processing creating a new Task each time it goes through the loop?
Something Like This?

VB.NET:
Private Sub GenNewPlayersASync()
     Dim x As Integer = 0
      'Generate the Players on an Async Thread
     Task.Factory.StartNew(Sub()
                           For i As Integer = 1 To NumPlayers
                           x = i
                           CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)).Wait()

                     ---->'Call Worker UpdateProgressBar(x, NumPlayers)

            
                           Next i
                           End Sub)

End Sub

And it doesn't help that in comparison to C#, VB.net is a nightmare for this type of stuff, lol
 
Last edited:
Well... In what class is GenNewPlayersASync? Is it in your viewmodel (it shouldn't be...)?

You should NOT have to call updateprogressbar. Just generate the players and stick them into a property. That is it. Your actual data should have no connection AT ALL to your UI. If you need to SHOW some data, then you show it through properties of your view model. Assuming the percentage done is relative to the number of players processed, that means you know in advance how many players there will be. So that also needs to be accessible from the ViewModel. So the view model can take the expected total number of players and do Players.Count / TotalPlayerCount, and then bind the form to that property.

I don't really see what happens in the rest of your code, so it's kind of hard to answer. I think you need to start with a simpler example and work from there. Starting from existing code will just give you the impression what is already there is good code, which it might not be. To work MVVM properly, you MUST have a good understanding of what is a good object oriented architecture. If you start with goofy procedural code and try to migrate to MVVM you will have issues.
 
Well... In what class is GenNewPlayersASync? Is it in your viewmodel (it shouldn't be...)?

You should NOT have to call updateprogressbar. Just generate the players and stick them into a property. That is it. Your actual data should have no connection AT ALL to your UI. If you need to SHOW some data, then you show it through properties of your view model. Assuming the percentage done is relative to the number of players processed, that means you know in advance how many players there will be. So that also needs to be accessible from the ViewModel. So the view model can take the expected total number of players and do Players.Count / TotalPlayerCount, and then bind the form to that property.

I don't really see what happens in the rest of your code, so it's kind of hard to answer. I think you need to start with a simpler example and work from there. Starting from existing code will just give you the impression what is already there is good code, which it might not be. To work MVVM properly, you MUST have a good understanding of what is a good object oriented architecture. If you start with goofy procedural code and try to migrate to MVVM you will have issues.

This was designed to be more of a quick and dirty type project to give the testers something to work with so they could test out the results from the player generation process and give me feedback, so I suppose I am trying to reverse engineer it to work in a way that I shouldn't be...I use MVVM in the main project and have it working pretty well. Figure I would test it out in this first because if I screw something up its not that big of a deal since its a lot less code, lol...
The GeneratePlayersAsync is in the GeneratePlayers Class, not the viewmodel...

OK, so basically I create a PlayerCount property in the viewmodel and assign it the value and then it kind of bypasses the issues with calling it inside of a UI thread? The number of players is entered in a textbox by the user.
 
Last edited:
Yup! That's really all there is to it.

Well, its still not working. Both UI and the new ProgressBar Window are unresponsive.

The ProgressBar task is running, the PlayerCount and PercentDone is updating properly, its just not displaying the updated values on the ProgressBar.

Here is the code in the GeneratePlayers Class---moving the task to encompass the entire block of code caused it to go much quicker, 45 seconds when it was creating a new task every iteration to 16-18 seconds now, but it still isn't working properly.

VB.NET:
 Private Sub GenNewPlayersASync()
        Dim x As Integer = 0
        'Generate the Players on an Async Thread
        Me.IsEnabled = False
        MyVM.Show()
        Dim mytask As Task = Task.Factory.StartNew(Sub()
                                                       For i As Integer = 1 To MyVM.TotalPlayers
                                                           x = i
                                                           CollegePlayers.GenDraftPlayers(x, MyDraft, DraftDT, DraftClass, PosCount)
                                                           MyVM.PlayerCount = x
                                                       Next i
                                                   End Sub)
        mytask.Wait()
        MyVM.Close()
        Me.IsEnabled = True
    End Sub

Here is the VM code:

VB.NET:
Public Sub New()


        ' This call is required by the designer.
        InitializeComponent()
        Dim mytask As Task = Task.Factory.StartNew(Sub() LoopPercent())
        ' Add any initialization after the InitializeComponent() call.
        'mytask.Wait()


End Sub


Public Property ProgressValue As Integer
        Get
            Return _ProgressValue
        End Get
        Set(value As Integer)
            _ProgressValue = value
            NotifyPropertyChanged("ProgressValue")
        End Set
    End Property


    Public Property PlayerCount As Integer
        Get
            Return _PlayerCount
        End Get
        Set(value As Integer)
            _PlayerCount = value
            NotifyPropertyChanged("PlayerCount")
        End Set
    End Property


    Public Property TotalPlayers As Integer
        Get
            Return _TotalPlayers
        End Get
        Set(value As Integer)
            _TotalPlayers = value
            NotifyPropertyChanged("TotalPlayers")
        End Set
    End Property


    Public Property PercentDone As Decimal
        Get
            Return _PercentDone
        End Get
        Set(value As Decimal)
            _PercentDone = value
            NotifyPropertyChanged("PercentDone")
        End Set
    End Property

Private Sub LoopPercent()
        'Dim mytask As Task(Of Decimal)
        While PercentDone < 100
            If PlayerCount > 0 Then
                PercentDone = CInt(CDbl(PlayerCount / TotalPlayers) * 100)
                ProgressText = PercentDone.ToString()
                Thread.Sleep(500)
            End If
        End While
    End Sub

And the XAML Code:

VB.NET:
<Window x:Class="ProgressBarDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WPFGeneration"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="Generating Players..."
        Width="300"
        Height="115"
        WindowStartupLocation="CenterOwner"
        WindowStyle="ToolWindow"
        mc:Ignorable="d">
    <Grid>
        <StackPanel Background="SlateGray">
            <Label x:Name="lblProgress"
                   HorizontalAlignment="Center"
                   Content="{Binding ProgressText}"
                   FontSize="16" />
            <ProgressBar x:Name="progress"
                         Height="25"
                         Background="DodgerBlue"
                         IsIndeterminate="False"
                         Value="{Binding PerecentDone}" />
            <StackPanel Margin="0,5,3,0"
                        HorizontalAlignment="Right"
                        Orientation="Horizontal">
                <Button x:Name="btnCancel" Click="btnCancel_Click">Cancel</Button>
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

Do I need to "Wait" the task in the VM?
 
Having glanced at some of Herman's posts, I'm wondering whether I may have misled you. I have used WPF some but not a lot and not in a multi-threaded environment. If modifying a bound property on a secondary thread does not create any issues then I'm sorry for the misinformation. Regardless, I'll leave you in Herman's hands as he seems to know more about WPF than I do.
 
Having glanced at some of Herman's posts, I'm wondering whether I may have misled you. I have used WPF some but not a lot and not in a multi-threaded environment. If modifying a bound property on a secondary thread does not create any issues then I'm sorry for the misinformation. Regardless, I'll leave you in Herman's hands as he seems to know more about WPF than I do.

Its no problem, I am always wanting to learn everything I can, so I will keep this for use in a Winforms or non-WPF environment...

I'm still confused as to why the UI thread is unresponsive even as I have a separate Task running for it...
 

Latest posts

Back
Top