Why is the GetReservations method not running asynchronously?

Tom Meier 140 Reputation points
2024-05-20T16:13:30.51+00:00

I am having trouble getting the GetReservations method to run asynchronously in my ReservationListingViewModel class in C#. Can someone please help me understand what I'm doing wrong? And I am also having an issue with using _reservations as private readonly. Thanks!

 public class ReservationListingViewModel:ViewModelBase
 {
     private readonly  ObservableCollection<ReservationViewModel> _reservations;

     public IEnumerable<ReservationViewModel>  Reservations => _reservations;

     
     public  ReservationListingViewModel()
     {
         InitializeAsync();

     }
     private async Task InitializeAsync()
     {
         _reservations = await GetReservations();
     }

     public async Task <ObservableCollection<ReservationViewModel>> GetReservations()
     {
         _reservations = new ObservableCollection<ReservationViewModel>();
         for (int i = 0; i < 1000; i++)
         {
             _reservations.Add(new ReservationViewModel(new Reservation("1,2", "Ron" + i, DateTime.Now, DateTime.Now)));
         }
         
         return _reservations;
     }

    

 }

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,418 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Michael Taylor 49,261 Reputation points
    2024-05-20T18:14:12.8166667+00:00

    I see a couple of problems.

    1. C# does not support async constructor calls. Therefore calls made in a constructor (ctor) must be sync. This is by design. If InitializeAsync is only called here then there is no reason to make it async because that isn't supported. You must wait for the async call to complete before the ctor returns. Since you're using an ObservableCollection then this is fine because when the collection is eventually populated then the UX will get notified anyway.
    public  ReservationListingViewModel()
    {
       //This must be called sync, C# doesn't support async ctor calls
       InitializeAsync().GetAwaiter().Wait();
    }
    

    Note however that if the method throws an exception then it'll be wrapped in AggregateException which is confusing in a ctor call so you might need to wrap the call in a handler. Calling async methods synchronously is generally not recommended.

    1. GetReservations is creating a new object and storing it in the field that the class is using. So as soon as the call starts you just stomped over your previous values. The InitializeAsync method technically does nothing. It takes the return value of the method (which is the value of _reservations) and stores it back into itself. Your GetReservations method should create and store the new collection in a temp variable. Then return the temp variable from the method. The InitializeAsync method can then assign it to the field, optionally after it does any post retrieval work.
    2. _reservations is readonly. That means its value can only be set in the constructor, not any other method. So the assignment in IntializeAsync (and GetReservations) is not allowed. In general fields should be readonly if they shouldn't change. In this case you're changing a field after creation so it cannot be readonly. Remove the modifier. In general it is not a good idea to wipe a field after you've set it up since anyone watching that original value would miss the changes. Since you're using IEnumerable here that isn't an issue but if you were to ever switch the property to ObservableCollection so you actually gain the benefit of using that type then code tied to the original field won't suddenly recognize a new field because they are using the original value instead.

    So rather than creating a new observable collection each time, you should create it readonly, get the new values and then clear the original collection and add the new values in. Something like this perhaps.

    public async Task InitializeAsync ()
    {
       var newItems = await GetReservations();
    
       //Clear original values
       _reservations.Clear();
       foreach (var item in newItems)
          _reservations.Add(item);
    }
    
    Note that this is going to trigger a lot of change notifications so you might want to "disable" any UX updates until you're done.
    

  2. Jiale Xue - MSFT 38,931 Reputation points Microsoft Vendor
    2024-05-21T03:14:21.56+00:00

    Hi @Tom Meier , Welcome to Microsoft Q&A,

    In C#, you cannot directly call an asynchronous method from a constructor. Constructors do not support the await keyword, which is required to run asynchronous code properly.

    A readonly field can only be assigned during the declaration or in a constructor. Since you are trying to assign _reservations in an asynchronous method (InitializeAsync), this violates the readonly constraint.

    public class ReservationListingViewModel : ViewModelBase
    {
        private ObservableCollection<ReservationViewModel> _reservations;
    
        public IEnumerable<ReservationViewModel> Reservations => _reservations;
    
        public ReservationListingViewModel()
        {
            // Initialize asynchronously
            InitializeAsync().ConfigureAwait(false);
        }
    
        private async Task InitializeAsync()
        {
            _reservations = await GetReservations();
        }
    
        public async Task<ObservableCollection<ReservationViewModel>> GetReservations()
        {
            var reservations = new ObservableCollection<ReservationViewModel>();
            for (int i = 0; i < 1000; i++)
            {
                reservations.Add(new ReservationViewModel(new Reservation("1,2", "Ron" + i, DateTime.Now, DateTime.Now)));
            }
    
            return reservations;
        }
    }
    

    Best Regards,

    Jiale


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment". 

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.