Xavier Peña
Xavier Peña

Reputation: 7919

How to create async method from sync

(C# code below)

I am just staring to learn about Async and Await. I've checked a few articles and tutorials, and I thought I got the concept behind it but I seem to hit the wall when implementing some practical examples.

This is the New() of my UI class:

Public Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    PopulateUI()

End Sub

...so the key here is PopulateUI(). I want to use this function without blocking the UI.

The only option I came out with which the compiler could accept is:

Private Async Sub PopulateUI()
    Await Task.Run(Sub()
            ' Do some stuff that populates the UI comboboxes
        End Sub)
End Sub

...this option does not work, and I think it is because Task.Run runs in a different thread so weird things occur when updating the comboboxes (sorry for being so vague about the description, I really don't know better).

So I found a similar SO question which didn't have any satisfactory answer, which makes me think is not that simple. Hopefully I'm wrong.


C# version:

public MyUI()
{
    // This call is required by the designer.
    InitializeComponent();

    // Add any initialization after the InitializeComponent() call.
    PopulateUI();
}

Private async void PopulateUI()
{
    await Task.Run(() => 
    {
        // Do some stuff that populates the UI comboboxes
    })
}

Upvotes: 2

Views: 3143

Answers (3)

Faizan Khan
Faizan Khan

Reputation: 172

Use Dispatcher when accessing the UI controls specifically while setting the properties of UI Controls, the weird things might go away. Fetch data from wherever in Task.Run but the code which is setting properties in UI Controls should be accessed using dispatcher. Probably this will be your issue.

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 457302

The db call should be awaitable, but it would just be changes would be quite costly for me at this point in time... So would it be a terrible idea to perform a Task.Run() of the fetch operation

As you noted, the ideal solution is to make it asynchronous all the way. So the solution below is a hack (to avoid too many code changes) and not a best practice.

That said, you can use async void and Task.Run here; you just have to be careful with your exception handling:

private async void PopulateUI()
{
  ... // Load initial view - "Loading..." message or whatever.
  try
  {
    var data = await Task.Run(() => 
    {
      ... // Read data from database.
    });
    ... // Update UI with data.
  }
  catch (Exception ex) // Not a typo. Catch all exceptions.
  {
    ... // Handle error - display message to user or whatever.
  }
}

Upvotes: 4

Krumelur
Krumelur

Reputation: 32597

First of all: be very careful with async void ...: you will not see any exceptions from such a call.

I don't know if this is the canonical pattern (if there is such a thing), but what I usually do is to create a method void CheckResult(Task task), which uses ContinueWith to append the necessary error handling (usually checking if the Task has faulted and logging/displaying an error dialog.

Then I would do something like:

public MyUI()
{
    // This call is required by the designer.
    InitializeComponent();

    // Add any initialization after the InitializeComponent() call.
    CheckResult(PopulateUI());
}

private async Task PopulateUI()
{
    var data = await FetchUiData();
    SetControlValues(data); // or whatever
}

private static void CheckResult(Task t) {
    // We want to be doing this on the UI thread
    var sched = TaskScheduler.FromCurrentSynchronizationContext();
    t.ContinueWith(() => {
       // check if "t" faulted and perform appropriate action
    }, sched);
}

Upvotes: 2

Related Questions