Robomato
Robomato

Reputation: 277

Waiting for Async calls to finish

I'm working with the Philips Hue, and I need to get some information from the hue bridge before I can populate my application. The requests are made via HTTP/JSON. I have no issue when I run all my code async, but when I try to break out my code so that I have a separate method to update the UI upon loading I'm getting a System.NullReferenceException on myLights. I'm assuming that's because my startUpProcedure() isn't finished yet, hence myLights has not been set. I can't seem to figure out how to wait for the startUpProcedure() to finish before I run setupUI().

To further back that up, if I just run startUpProcedure() without setupUI() I get no issues. And then if I run setupUI() from say a button click it runs just fine.

Clearly I'm missing something here. I just can't seem to find the answer.

So to succinctly put the question: How do I wait for these Async calls to finish so that I can use variables that are depended on their return values?

 public sealed partial class MainPage : Page
 {

    public string appKey;
    public string myIP;
    public IEnumerable<Light> myLights;
    public ILocalHueClient myClient;

    public MainPage()
    {
        this.InitializeComponent();

        startUpPocedure();


        setupUI();

    }

    public async Task startUpPocedure()
    {
        await startUp();

        await getLights();
    }

    public async Task startUp()
    {
        if (await findBridgeIP())
        {
            Debug.WriteLine("Bridge Found...");
            //Do Actions
        }
        else
        {
            //Error!!
            Debug.WriteLine("No hue found");
        }

        Windows.Storage.ApplicationDataContainer localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;
        Windows.Storage.StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;

        try {
            appKey = localSettings.Values["appKey"].ToString();

            Debug.WriteLine("appKey loaded: " + appKey);

            //Load up 
            myClient = new LocalHueClient(myIP);

            myClient.Initialize(appKey);

        }
        catch {
            Debug.WriteLine("Need to register app");
        }

    }

    async Task getLights()
    {

        myLights = await myClient.GetLightsAsync();

        Debug.WriteLine("Light Count " + myLights.Count());

        IEnumerable<string> myLightNames;

        List<string> myTempList = new List<string>();

        foreach (Light l in myLights)
        {
            myTempList.Add(l.Name);
            Debug.WriteLine(l.Name);
        }

        myLightNames = myTempList;

        comboBox_LightSelect.ItemsSource = myLightNames;

    }

    private void setupUI()
    {
        //Populate the Combo Box
        IEnumerable<string> myLightNames;

        List<string> myTempList = new List<string>();

        foreach (Light l in myLights)
        {
            myTempList.Add(l.Name);
            Debug.WriteLine(l.Name);
        }

        myLightNames = myTempList;

        comboBox_LightSelect.ItemsSource = myLightNames;


    }

Upvotes: 0

Views: 261

Answers (2)

Lex Li
Lex Li

Reputation: 63295

You should leave only InitializeComponent in the constructor, and move all other logic to Loaded event handler,

https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.frameworkelement.loaded

Then you can mark that handler as async, and use await in it to await on async methods.

Upvotes: 1

Andrei Ashikhmin
Andrei Ashikhmin

Reputation: 2451

Your startUpPocedure method in MainPage constructor returns Task almost immediately, and because you're not awaiting it, code execution goes to the next line right after that and setupUI gets called. So when your code starts to enumerate myLights collection, it's still null because startUpPocedure and therefore getLights are still running.

Problem is, you can't await asynchronous methods in a constructor, so in your case solution will be to move both startUpPocedure and setupUI to single async method, await them inside this method and call it from constructor similar to this:

public MainPage()
{
    this.InitializeComponent();    
    Startup();        
}

private async void Startup()
{
    await startUpPocedure();   
    setupUI();
}

Upvotes: 1

Related Questions