Reputation: 529
I am deserializing a list of objects from an XML file, and would like to bind to the actual content of those objects in my View, passing over a ViewModel. The problem is that file operations are async
and this bubbles all the way up to the ViewModel, where Property getters cannot be marked as such...
I deserialize all XML files in a folder to Profile
objects and store them in a List<Profile>
. This method (has to be) marked async
.
public static async Task<List<Profile>> GetAllProfiles()
{
DataContractSerializer ser = new DataContractSerializer(typeof(Profile));
StorageFolder folder = await ApplicationData.Current.RoamingFolder.CreateFolderAsync("Profiles", CreationCollisionOption.OpenIfExists);
List<Profile> profiles = new List<Profile>();
foreach (var f in await folder.GetFilesAsync())
{
var fs = await f.OpenStreamForReadAsync();
profiles.Add((Profile)ser.ReadObject(fs));
fs.Dispose();
}
return profiles;
}
The binding property in my ViewModel would then ideally call that static method like this
public async Task<ObservableCollection<string>> Lists
{
get
{
return new ObservableCollection<string>(GetAllProfiles().Select(p => p.Name));
}
}
BUT Properties cannot be marked async
public ObservableCollection<string> Lists
{
get
{
return new ObservableCollection<string>((GetAllProfiles().Result).Select(p => p.Name));
}
}
await folder.GetFilesAsync()
call for some reason)Calls an async
Initialize() method that loads the result of the GetProfiles()
function in a variable, and then makes a NotifyPropertyChanged("Lists")
call:
public ViewModel()
{
Initialize();
}
public async void Initialize()
{
_profiles = await Profile.GetAllProfiles();
NotifyPropertyChanged("Lists");
}
private List<Profile> _profiles;
public ObservableCollection<string> Lists
{
get
{
if (_profiles != null)
return new ObservableCollection<string>(_profiles.Select(p => p.Name));
else
return null;
}
}
Is there a better way? Is there a pattern/method that I haven't yet discovered?
The root of the problem appears when doing non-UI code, and you cannot rely on the NotifyPropertyChanged to do some thread-synchronization stuff. -- The method Initialize has to be awaited and ctors cannot be async, so essentialy this is pattern is useless.
public MyClass()
{
Initialize();
}
public async void Initialize()
{
_profiles = await Profile.GetAllProfiles();
}
private ObservableCollection<Profile> _profiles;
public ObservableCollection<string> Lists
{
get
{
return _profiles; // this will always be null
}
}
Upvotes: 2
Views: 2091
Reputation: 31724
Properties can't be async so this solution will not work as you mentioned. Task.Result waits for the task to complete, but this is blocking your UI thread where the I/O operation's async callback returns, so you are deadlocking your application, since the callback is never called. Your solution really is the best way. It could be improved though.
Upvotes: 1