I'm sorry to ask such a basic/stupid question, but I'm lost. I've spent 3 days working on this stupid problem and can't find a solution.
My problem is that the program freezes as soon as async methods are involved.
My code is below. First, I define some global parameters, then in public MainPage(), the first task is to call method "OpenFile()" to get information from a .txt file (containing coutries and correponding capitals)
namespace WorldCapitalsv2
{
public sealed partial class MainPage : Page
{
//Some global parameters
string paysATrouver = null; string capitaleATrouver = null;
int scoreJoueur; int nombreParties; int HighScore; Random rand = new Random();int nbCountries;
List<Pays> listCountries = new List<Pays>();
HashSet<int> checkRnd = new HashSet<int>();
int[] propositionRepNb = new int[4];
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
InitializeComponent();
openFile(); //Program freezes here :(
nbCountries = listCountries.Count;
}
async void openFile()
{
Uri capitaleFileLoc = new Uri("ms-appdata://Capitale.txt");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(capitaleFileLoc);//.AsTask().ConfigureAwait(false);
var list = await FileIO.ReadLinesAsync(file);//.AsTask().ConfigureAwait(false);
foreach (string country in list)
{
string[] countrycap = country.Split('\t');
listCountries.Add(new Pays() { nomPays = countrycap[0], Capitale = countrycap[1] });
}
}
It is very frustrating, because it is otherwise working like a charm, e.g. rather than using a .txt file I put the information in the main program, by putting :
List<Pays> listePays = new List<Pays>(){
new Pays() {nomPays="France", Capitale="Paris"},
... etc
However, i'd rather call and read an external file.
I have tried to add an output parameter to my async openFile method, e.g :
async Task<List<Pays>> openFile()
{
Code;
return ListCountries;
}
And tried to get the data in MainPage() with :
Task<List<Pays>> listePaysT = ouvreFichier();
listePaysT.Start();
listePaysT.Wait();
listePays = listePaysT.Result;
But this solution is not working ....
Also, if I try to call my method openFile() with an await it will not work (I understood await calls are not possible in the MainPage()).
I'd be forever gratefull to anybody who could help solve this problem. Thanks.
More details below : here is the interface: a very basic 1 page only app.
<Page
x:Class="WorldCapitalsv2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WorldCapitalsv2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<TextBlock x:Name="TexteAppTitle" HorizontalAlignment="Left" Margin="0,0,0,585" TextWrapping="Wrap" Text="World Capitals" Width="400" RenderTransformOrigin="0.5,0.5" FontSize="48"/>
<Button x:Name="btn_prop1" Content="Button" HorizontalAlignment="Left" Margin="27,261,0,0" VerticalAlignment="Top" Height="70" Width="343" FontSize="24" Click="Reponse1"/>
<Button x:Name="btn_prop2" Content="Button" HorizontalAlignment="Left" Margin="27,326,0,0" VerticalAlignment="Top" Height="70" Width="343" FontSize="24" Click="Reponse2"/>
<Button x:Name="btn_prop3" Content="Button" HorizontalAlignment="Left" Margin="27,391,0,0" VerticalAlignment="Top" Height="70" Width="343" FontSize="24" Click="Reponse3"/>
<Button x:Name="btn_prop4" Content="Button" HorizontalAlignment="Left" Margin="27,457,0,0" VerticalAlignment="Top" Height="70" Width="343" FontSize="24" Click="Reponse4"/>
<TextBox x:Name="labelQuestion" HorizontalAlignment="Left" Margin="10,97,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="380" Height="101"/>
<TextBox x:Name="labelScore" HorizontalAlignment="Left" Margin="27,532,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="145" Height="101"/>
<TextBox x:Name="labelHighScore" HorizontalAlignment="Left" Margin="223,532,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="145" Height="101"/>
</Grid>
I've put all my methods in the "MainPage.xaml.cs" and nothing more than initially created by VS in "App.xaml.cs"
This is the problem:
listePaysT.Wait();
That will block until the task has completed... so you're blocking your UI thread. Worse, the async methods will need to return to the UI thread in order to continue, so you've got a deadlock.
Task.Wait()
and Task.Result
can only be used very carefully.
You should think about exactly what you want to happen. You can't make the constructor async, but you could potentially make the page constructor complete and then call a separate async method to populate its data. (It's not clear why you've got all this code in the UI layer anyway - shouldn't it be in the view-model?)
Basically, you should think about what you want to happen before the data is available - do you want the UI to be present but not contain the data, or do you want the MainPage
to only be available after the data has been fetched?
You can sort of mimic an async constructor with a static async method:
public static async MainPage CreatePageAsync()
{
var ret = new MainPage(); // This would be the normal UI code
await ret.LoadDataAsync(); // This would asynchronously fetch the data
}
Also note that currently you're calling InitializeComponent
twice, which can't be a good idea...
See more on this question at Stackoverflow