Calling Task-based Asynchronous Methods from Class Constructors
Task-based asynchronous pattern has many advantages over other asynchronous patterns introduced in the past, most of them boiling down to the fact that it's really easy to get into and start using it. Like any other technology, it does have its pitfalls and there are many details to know about once you get into more advanced scenarios.
One of the first issues you'll very probably stumble upon, is the viral nature of asynchronous methods, meaning that as soon as a method calls one asynchronous method, it becomes asynchronous as well. In most cases that's not really a problem: you just change method signatures by adding the async
keyword and changing the return value (T
to Task<T>
and void
to Task
) up the call stack until you reach the originating call, usually an event handler. You just make it async void
and you're done.
The problem arises when you're trying to call such an asynchronous method from a class constructor: you can't use the await keyword inside the constructor. As long as you're only using methods which don't return a value, you can get away with it by just calling them without await, but that's not the right way to do it. That's why the compiler issues a warning in this case: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the await
operator to the result of the call.
Of course, there are ways to properly get around this limitation. Most of the time you don't care when the asynchronous part of the constructor is going to be completed. This happens in view models, for example. You start loading data in the constructor and when its loaded, the values get assigned to public properties implementing INotifyPropertyChanged
interface and in turn cause the UI to refresh. In this case all asynchronous calls can be grouped in a single async void
asynchronous method which can be called from the constructor:
public ViewModel()
{
Init();
}
private async void Init()
{
var file =
await Package.Current.InstalledLocation.GetFileAsync(@"Assets\Text.txt");
Text = await FileIO.ReadTextAsync(file);
}
public string Text
{
get { return _text; }
set
{
if (value == _text) return;
_text = value;
OnPropertyChanged();
}
}
You can expand on that same idea by adding a bool Initialized
property to your view model. By setting it to true
at the end of Init()
, you can use it to display a different view (e.g. an indeterminate progress indicator) until the initialization is complete (I added a short delay to make the effect easier to notice):
public class BooleanToVisibilityConverter : IValueConverter
{
public bool Inverted { get; set; }
public object Convert(object value, Type targetType,
object parameter, string language)
{
if (!(value is bool))
{
return Visibility.Collapsed;
}
return (bool)value ^ Inverted ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
}
<Page.Resources>
<local:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<local:BooleanToVisibilityConverter x:Key="InvertedBoolToVisibilityConverter"
Inverted="True" />
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ProgressRing IsActive="True"
Visibility="{Binding Initialized,
Converter={StaticResource InvertedBoolToVisibilityConverter}}"/>
<TextBlock Text="{Binding Text}"
TextWrapping="Wrap"
Visibility="{Binding Initialized,
Converter={StaticResource BoolToVisibilityConverter}}"
Style="{StaticResource BasicTextStyle}" />
</Grid>
public ViewModel()
{
Init();
}
private async void Init()
{
var file =
await Package.Current.InstalledLocation.GetFileAsync(@"Assets\Text.txt");
Text = await FileIO.ReadTextAsync(file);
await Task.Delay(2000);
Initialized = true;
}
public string Text
{
get { return _text; }
set
{
if (value == _text) return;
_text = value;
OnPropertyChanged();
}
}
public bool Initialized
{
get { return _initialized; }
set
{
if (value.Equals(_initialized)) return;
_initialized = value;
OnPropertyChanged();
}
}
The next improvement on the idea would be providing progress report during initial loading. Since everything is being processed inside the same view model class, this could be achieved in many ways, but lets take the IProgress<T>
approach as defined by the task-based asynchronous pattern (again I'm using delays to simulate loading times):
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ProgressBar Value="{Binding Progress}"
Minimum="0"
Maximum="1"
Visibility="{Binding Initialized,
Converter={StaticResource InvertedBoolToVisibilityConverter}}"/>
<TextBlock Text="{Binding Text}"
TextWrapping="Wrap"
Visibility="{Binding Initialized,
Converter={StaticResource BoolToVisibilityConverter}}"
Style="{StaticResource BasicTextStyle}" />
</Grid>
public ViewModel()
{
var progress = new Progress<double>();
progress.ProgressChanged += (s, v) => Progress = v;
Init(progress);
}
private async void Init(IProgress<double> progress)
{
var file =
await Package.Current.InstalledLocation.GetFileAsync(@"Assets\Text.txt");
Text = await FileIO.ReadTextAsync(file);
for (int i = 0; i < 10; i++)
{
await Task.Delay(500);
progress.Report((double)i/10);
}
Initialized = true;
}
public string Text
{
get { return _text; }
set
{
if (value == _text) return;
_text = value;
OnPropertyChanged();
}
}
public double Progress
{
get { return _progress; }
set
{
if (value.Equals(_progress)) return;
_progress = value;
OnPropertyChanged();
}
}
public bool Initialized
{
get { return _initialized; }
set
{
if (value.Equals(_initialized)) return;
_initialized = value;
OnPropertyChanged();
}
}
If you don't want a loading view but need to check whether initialization is complete at a later time you can change Init()
to return a task and store a reference to it:
private readonly Task _initializingTask;
public ViewModel()
{
_initializingTask = Init();
}
private async Task Init()
{
var file =
await Package.Current.InstalledLocation.GetFileAsync(@"Assets\Text.txt");
Text = await FileIO.ReadTextAsync(file);
}
private async void OnSomeCommand()
{
// check if initialization is complete
if (_initializingTask.IsCompleted)
{
// do some action
}
// or even await the initialization to complete if it hasn't already
await _initializingTask;
}
Using one of the techniques above you should be able to call any asynchronous method in a class constructor and handle it appropriately. As always modify the implementation details to your actual needs.