1

I have a simple app where I show a progress ring when some task is running and hide the progress ring as soon as it completes. Problem with this code is the Progress bar never gets collapsed. I keep a break point in the value converter class and it never receives a value of false even after the value changes. As a result the ProgressRing never collapses. Please help.

This is my ViewModel

public class TestVM : INotifyPropertyChanged
    {
        private bool _isRingVisible;
        public bool IsRingVisible
        {
            get => _isRingVisible;
            set
            {
                _isRingVisible = value;
                OnPropertyChanged(nameof(IsRingVisible));
            }
        }
        public TestVM()
        {
            Task.Run(async () => await DoSomething());
        }

        private async Task DoSomething()
        {
            IsRingVisible = true;
            await Task.Delay(5000);
            IsRingVisible = false; //Value set to false but when I have a break point in the value converter class, it never receives this value.
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

In the xaml I have a simple UI as follows,

<Page.Resources>
        <converter:BoolToVisibilityConverter x:Key="boolToVisibility"/>
    </Page.Resources>
<Grid>
        <Border x:Name="BdrProgressRing" 
                    Grid.Row="0" 
                    Grid.RowSpan="2" 
                    Background="Red"
                    VerticalAlignment="Center"
                    Opacity="0.6" 
                    Visibility="{x:Bind vm.IsRingVisible,Mode=OneWay,Converter={StaticResource boolToVisibility}}">
        </Border>
        <ProgressRing x:Name="PgRing" 
                          Grid.Row="0" 
                          Grid.RowSpan="2"
                          Visibility="{Binding ElementName=BdrProgressRing, Path=Visibility}"
                          IsActive="True"  
                          VerticalAlignment="Center"
                          Width="90"
                          Height="90"/>
    </Grid>

Here is my xaml.cs

public sealed partial class MainPage : Page
    {
        public TestVM vm { get; set; }
        public MainPage()
        {
            this.InitializeComponent();
            vm = new TestVM();
            this.DataContext = this;
        }
    }
Clemens
  • 123,504
  • 12
  • 155
  • 268
nikhil
  • 1,578
  • 3
  • 23
  • 52
  • 1
    Thanks it works. Since I called it inside a Task I need to use a Dispatcher I guess. – nikhil Jul 01 '20 at 21:49
  • Sure I will read about it thanks. – nikhil Jul 01 '20 at 21:53
  • Don't use `Dispatcher` if possible. It can give the code a monster-look. If you want to send back to UI Thread something from concurrent `Task` in the middle of execution, you may use a synchronized callback class `Progress` which implements `IProgress` interface. It's simple as while-true. It executes its delegate in that Context where it was constructed. – aepot Jul 01 '20 at 22:10

1 Answers1

1

Change

Task.Run(async () => await DoSomething()) ;

To

_ = DoSomething();

Probably you allowed to change the property only from Main UI Thread, not from pooled Task. Learn more about Synchronization Context in WPF.


However, the above is bad practice. Any async method should be awaited. Simply assigning the Task returned from DoSomething() to a local variable is not sufficient.

Since you can't await in a constructor, the view model should have a public awaitable method that is actually awaited by the caller, e.g.

public TestVM()
{
}

public Task Initialize()
{
    return DoSomething();
}

Then call await vm.Initialize(); in an async Loaded event handler in the view:

public MainPage()
{
    InitializeComponent();
    vm = new TestVM();
    DataContext = this;

    Loaded += async (s, e) => await vm.Initialize();
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
aepot
  • 4,558
  • 2
  • 12
  • 24
  • Hi @aepot can you help me with this post https://stackoverflow.com/questions/62687039/property-changed-is-not-updating-the-ui-from-inside-a-task I thought its same as this one but it didn't solve my problem there. – nikhil Jul 02 '20 at 00:11
  • @Clemens thanks for the useful edits. I prefer to keep it. But i can't see the technical difference between direct call like Fire-and-Forget and `async void` lambda which used for `Load` in the updated answer. The logical difference is where you must handle the possible exceptions: in `DoSomethingAsync` or in lambda. Thus i can't say that's unsafe Fire-and-Forget is bad practice. In both ways the developer must take care of possible `Exception` catching. Also i like [this](https://johnthiriet.com/removing-async-void/) approach. – aepot Jul 02 '20 at 08:02
  • It's not about any exceptions. The point is to *always* await an awaitable method - except event handlers, where this isn't possible. – Clemens Jul 02 '20 at 08:10
  • @Clemens In the same manner you may create some `async void`, it's not awaitable. Btw, `async void` Event handler is just an `async void` method, technically no difference. Maybe `async void` is better than discarding `async Task` (which you may `await` in another `async` method in the code because this method may be called not only in `.ctor`), I don't know. Ok, let it be. Thanks! – aepot Jul 02 '20 at 08:30
  • 1
    You *never* declare an `async void` method, except when it is an event handler. – Clemens Jul 02 '20 at 08:42
  • @Clemens agree, but it looks like a kind of superstition. Why I can't use some implementation when it's exaclty what I need? Wrapping fire-and-forget call into Event Handler it's redunant and tangled thing especially for this exact case. If discarding `Task` is bad practice, it's ok. I agree but then I prefer `async void` wrapper with `try-catch` inside. And yes, I'll keep this secret way to fire-and-forget and will not tell anyone. _Let me know when you'll done reading this, I'll delete this message. :)_ – aepot Jul 02 '20 at 10:35
  • 1
    "*I can't use some implementation when it's exaclty what I need?* . that's not the point. Keep in mind you are writing an answer on StackOverflow, which is read by many people that aren't experts. – Clemens Jul 02 '20 at 11:21
  • @Clemens _that's not the point_ Got it. I just know how it works and it doesn't allow me sleep. Thank you for the proper explanations, I have no more objections on it. – aepot Jul 02 '20 at 13:16