Home > Enterprise >  What to do when I can't await an async call
What to do when I can't await an async call

Time:12-18

I am working on an old WinForms application. I'm trying to make a piece of code run asynchronously, so it doesn't block the UI thread. I'm having some difficulties applying the pattern. Here's a simplified version of my code (before modifications):

void InitializeMyGui()
{
  //some code
  if(someObject.SomeConditionIsMet())
  {
    //some code
  }
  else
  {
    //some other code
  }
  // some more code
 }

This method is called from two places. One is the constructor:

public MyGui()
{
  // some code
  InitializeMyGui();
  // some more code
}

The other is a method that is triggered by an event handler (this application is older than async/await, event driven programming was used to achieve asynchronous execution)

private void OnSomeOtherStuffFinishedLoading(object sender, EventArgs e)
{
   // some code
   InitializeMyGui();
   // some more code
}

Problem is SomeConditionIsMet() contains a database call, so it needs to be asynchronous to avoid long operations blocking the UI thread if the DB call is slow. No worries, I've modified my methods thusly:

public async Task<bool> SomeConditionIsMet() {
 //some code
 await dbAdapter.ExecuteAsync();
 //process and return result
}

Now InitializeMyGui() obviously needs to await SomeConditionIsMet():

async void InitializeMyGui()
 {
    //some code
    bool conditionMet = await someObject.SomeConditionIsMet();
    if(conditionMet)
    {
      //some code
     }
     else
     {
     //some other code
     }
     // some more code
  }

And herein lies the problem. InitializeMyGui() is now async, and should be awaited. Indeed, everything I've read regarding async/await explicitly states you should await async calls all the way to the top. But while I could probably make OnSomeOtherStuffFinishedLoading() async and have it await InitializeMyGui(), I can't make the constructor async.

In this case, it's not such a big deal. InitializeMyGui() is a void, it's not returning any value. But if I understand it correctly, if I don't await the method, the execution will continue before it returns, and the code that comes after, (in my pseudocode marked // some more code) could - and in all likelihood will - execute before InitializeMyGui() finishes.

What I'm missing is a way to say "Hey, run this async method, and then continue with this code here, but do it all asynchronously so that you're not blocking the UI thread" but without making the calling mehtod itself async. Is there a way to do this?

EDIT:

I came up with this:

Task.Run(InitializeMyGui()).ContinueWith(t => {// some other code});

Will this work, or am I somehow violating the sanctity of the UI thread?

CodePudding user response:

Make the constructor private, and use a static factory method to call it and your initialization code. A static method can of course be async.

class MyGui
{
    private MyGui()
    {
    }

    private async Task InitializeMyGui()
    {
    }

    static public async Task<MyGui> CreateInstance()
    {
        var instance = new MyGui();
        await instance.InitializeMyGui();
        return instance;
    }
}

Now you can instantiate an instance using async code:

var myInstance = MyGui.CreateInstance();

CodePudding user response:

First off, InitializeMyGui should be async Task, not async void.

To solve your problem, you need to accept that you now have an additional state in your application. Previously, the code would block the UI thread until it was ready to show the complete UI (after the DB call). What you're doing by making it asynchronous is freeing up that UI thread. But then the UI has to show something. So what you really have is a new "loading" kind of state in your app.

So, the best solution is to have your constructor (synchronously) initialize to the loading state, and then have it update to the loaded state. I wrote an article about this technique - it uses MVVM/WPF, but the concepts are the same.

The natural way to do this is to use async/await rather than ContinueWith:

public MyGui()
{
  // some code

  Loaded = ContinueConstructionAsync();

  async Task ContinueConstructionAsync()
  {
    await InitializeMyGui();

    // some more code
  }
}

public Task Loaded { get; }

This code also adds a new property that can be awaited if other parts of the code depend on this type being loaded.

  • Related