Async/Await a source of constant misunderstanding

JasinCole

Well-known member
Joined
Feb 16, 2023
Messages
66
Programming Experience
1-3
I've been trying to wrap my head around this Async/Await pattern and how it is meant to be handled inside a SychronousContext when programming a GUI. There seems to be so many pieces and I think I am just confusing myself trying to research and implement ideas that i've actually hinderd my ability to learn anything meaningful.

As an example of my recent struggles, I am trying to build a dashboard that will make calls to a database using lots of different methods to gather the data and update my viewmodel properties.
What I would like to do is have an overall method that fires off a bunch of other asynchronous methods, when all those methods are finished and complete I want to access the results of those calls and update my viewmodel. For instance... My understanding is this approach will fire off each task and run them Asynchrounously. Then I await each task to completion and assign the result to the property.

C#:
public async Task LoadAccountBalancesAsync()
{
    var checkingTask = GetAccountAsync(int:<accountnum>);
    var savingsTask = GetAccountAsync(int:<accountnum>);
    var payrollTask = GetAccountAsync(int:<accountnum>);
    
    //Etc...
    
    Checking = await checkingTask;
    Savings = await //...
    Payroll = await //...
}

That would seem to work, but it's redudant and not very approachable when the amount of information I need is 3 to 4 times that amount or more.
What I'd like to do is
C#:
public async Task LoadAccountBalancesAsync()
{
    var tasks = Task[]
    {
        GetAccountAsync(int:<accountnum>);
        //etc...
    }
    
    await Task.WhenAll(task);
    
    //Update properties... But how?
}

Or even better would be to
C#:
public async Task LoadAccountBalances()
{
    var tasks = new List<Task>();
    
    foreach (var a in accounts)
    {
        tasks.Add(GetAccountAsync(a))
    }
    
    await Task.WhenAll(tasks);
    
    //But again how do I access the result of the task?
}

How would you do it and why would you use that approach? Is this true Asynchronization? I really like the idea of grouping that task together, `WhenAll()` to get a single state, that way I am not updating the dashboard with some valid and other non-valid date if an exception is thrown.

The next logical step in my brain would be the expand this further. Like so...
C#:
public async Task LoadDashBoard()
{
    //Run as Task Asynchronous
    var tasks = Task[]
    {
        LoadAccountBalances();
        LoadOutstandingAccountsReceivables();
        LoadOutstandingAccountsPayables();
        LoadJobOverUnder();
        LoadOustandingChangeOrders();
    }
    
    await Task.WhenAll(tasks);
}

Would appreciate a little guidance and some verbose thought process to why and how you would approach the problem.
 
Recommended reading:

DO NOT SKIP the 3 articles linked to at the beginning.
 
Stephen Cleary's response linked below is relevant to the meta question about initializing properties asynchronously.

 
Thanks for the links and information. I'm still not quite there yet. While I understand the concept of async/await in the general sense.

Things that just confuse me...
1. At some point you must call an async method from a non-async method. Which seems like an oxymoron when Stephen Cleary says "Async all the way". The question then becomes where is the disconnect and why is it appropriate in that context? There is like a black box I've yet to fully understand that black box. In the UI sense, why are events that disconnect? Does an event really need to be an async void method? Shouldn't they just fire off some task and be done? It seems odd to return to an event handler...

2. In a UI it seems weird to me using events to intialize properties used for data binding. It seems much more logical to intialize those properties during object intialization. I guess the only way to do this is using a factory pattern, that way the object creation can be async. But this trivial pattern seems to get very difficult when using UI/MVVM patterns and IoC DI. As you don't really have direct control over the creation of those objects. This seems like a chicken and the egg problem, one hand you are making life easier for yourself, the other hand you are making things more complicated.

3. the name 'await' just throws me off. (first impressions are hard to break)

After reading this Async OOP Properties
Data binding will not give awaitable types any special treatment, so the type of an “asynchronous property” used for data binding must be the type of the result of the asynchronous operation (e.g., int instead of Task<int>).
it seems Avalonia UI does indeed give special consideration to Async Properties Data Binding Task
But I do not really understand how to use this. await unwraps the Task<TResult>. Any call to an async function with await would unwrap and therefor wouldn't be assignable to a property typof(Task<>)
 
I'll be damned but I figured out a way to make this work. I guess I was missing that if you return an await Task it doesn't unwrap. This is what I have so far and at the moment is working. I am taking advantage of the Avalonia Data Binding to Task

C#:
private void HomePage_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
    {
        (DataContext as HomePageViewModel)?.InitializeAsync();
    }

C#:
public partial class HomePageViewModel : ViewModelBase
{
    [ObservableProperty] private Task<IEnumerable<Lgract>> _accounts;

    public HomePageViewModel(IUnitOfWork srvc) : base(srvc)
    {
    }

    public async Task InitializeAsync()
    {
        var task = new[]
        {
            Accounts = LoadAccountBalances(),
        };

        await Task.WhenAll(task);
    }
   
    [RelayCommand]
    private async Task<IEnumerable<Lgract>> LoadAccountBalances()
    {
        var accounts = new List<LedgerAccount>
        {
           new LedgerAccount("General", 1000),
           new LedgerAccount("Payroll", 1002),
           new LedgerAccount("Savings", 1030),
           new LedgerAccount("Certificate of Deposit", 1040)
        };
       
        return await Srvc.Lgracts.GetAccountsByRecnumAsync(accounts.Select(act => act.Number).ToList());
    }
}
 
This line is just making a synchronous call:
C#:
(DataContext as HomePageViewModel)?.InitializeAsync();
 
I have my doubts about the author's assertion that the method call will not block. Not at a PC right now to put together a quick test/demo. Perhaps, I am wrong and it truly does not block. But if so that would then you can just call any method marked as async and those methods won't block, and therefore there is no need to use await anywhere.
 
I have my doubts about the author's assertion that the method call will not block. Not at a PC right now to put together a quick test/demo. Perhaps, I am wrong and it truly does not block. But if so that would then you can just call any method marked as async and those methods won't block, and therefore there is no need to use await anywhere.
 
Well I have no fundamental assertion as to why that is the case. But, that is not the only article I’ve read that stated a similar sentiment.

This is kind of why I want to have an open discussion around this. Because the information seems to be a little all over the place. I’d like to actually see some code but my limited knowledge doesn’t really allow me to write test assertions that prove or disprove said sentiments that I read.
 
Seems to block for me:
C#:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading.Tasks;

namespace WinForms
{
    class MainForm : Form
    {

        MainForm()
        {
            Text = "Main";
            Size = new Size(800, 600);

            var lblTime = new Label
            {
                Text = DateTime.Now.ToString(),
                Dock = DockStyle.Fill
            };

            var btnTest = new Button
            {
                Text = "Test",
                Dock = DockStyle.Top
            };
            btnTest.Click += btnTest_Click;

            Controls.Add(lblTime);
            Controls.Add(btnTest);
            CenterToScreen();

            bool isWhite = true;
            var timer = new Timer() { Interval = 500 };
            timer.Tick += (o, ea) =>
            {
                isWhite = !isWhite;
                lblTime.ForeColor = isWhite ? Color.Black : Color.White;
                lblTime.BackColor = isWhite ? Color.White : Color.Black;
                lblTime.Text = DateTime.Now.ToString();
            };
            timer.Start();
        }

        void btnTest_Click(object sender, EventArgs e)
        {
            _ = DoWorkAsync();
        }

        async Task DoWorkAsync()
        {
            this.Text = "Working...";
            await DoCpuIntensiveWork();
            this.Text = "Main";
        }

        static Task<bool> DoCpuIntensiveWork()
        {
            // Simulate CPU intensive work by making the thread sleep
            var tcs = new TaskCompletionSource<bool>();
            System.Threading.Thread.Sleep(5000);
            tcs.SetResult(true);
            return tcs.Task;
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}
 
I guess I was missing that if you return an await Task it doesn't unwrap

Await unwraps, but you're doing your return await inside an async method, which wraps

You could remove the async modifier and the await

At some point you must call an async method from a non-async method
Technically you can always call an async method from a non-async, but I guess you're talking about leveraging asyncronocity i.e. awaiting the async method's completion then proceeding with the rest of the sync method?
 
Last edited:
Await unwraps, but you're doing your return await inside an async method, which wraps
That seems kind of dumb, but I assume that is indeed the correct approach?

Technically you can always call an async method from a non-async, but I guess you're talking about leveraging asyncronocity i.e. awaiting the async method's completion then proceeding with the rest of the sync method
I guess my hangup here is the quote 'Async all the way', when eventually you always seem to run into a synchronous issue(code you can't change) or into an async void method which is code smell (unless it is an event handler)

I'm trying to develop good habits, but I always get hung up on these black boxes. When I try to reason about them my only reasoning device is the words I read and I take that at 100% face value. Contrived examples are the worse, because they never reflect real problems and never share with you the pitfalls. Which I believe is more important.

Async tips <- I've watched Tip #1 video and learned more about Async/Await then I did reading any article. I'm going to watch the rest of the videos soon.

The one I will say is that I do not understand the 'Context' clearly. I understand again the basics of what it is how it is needed etc. But I couldn't tell you by looking at code what context the await state machine is capturing... if I even said that right
 
Last edited:
Seems to block for me:
C#:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading.Tasks;

namespace WinForms
{
    class MainForm : Form
    {

        MainForm()
        {
            Text = "Main";
            Size = new Size(800, 600);

            var lblTime = new Label
            {
                Text = DateTime.Now.ToString(),
                Dock = DockStyle.Fill
            };

            var btnTest = new Button
            {
                Text = "Test",
                Dock = DockStyle.Top
            };
            btnTest.Click += btnTest_Click;

            Controls.Add(lblTime);
            Controls.Add(btnTest);
            CenterToScreen();

            bool isWhite = true;
            var timer = new Timer() { Interval = 500 };
            timer.Tick += (o, ea) =>
            {
                isWhite = !isWhite;
                lblTime.ForeColor = isWhite ? Color.Black : Color.White;
                lblTime.BackColor = isWhite ? Color.White : Color.Black;
                lblTime.Text = DateTime.Now.ToString();
            };
            timer.Start();
        }

        void btnTest_Click(object sender, EventArgs e)
        {
            _ = DoWorkAsync();
        }

        async Task DoWorkAsync()
        {
            this.Text = "Working...";
            await DoCpuIntensiveWork();
            this.Text = "Main";
        }

        static Task<bool> DoCpuIntensiveWork()
        {
            // Simulate CPU intensive work by making the thread sleep
            var tcs = new TaskCompletionSource<bool>();
            System.Threading.Thread.Sleep(5000);
            tcs.SetResult(true);
            return tcs.Task;
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }
}

Here is the article that I really wanted to share with you over the async void event handler. Scroll to Removing Async Void section. What exactly is John suggesting here? Is he wrong? What is the end result of his assertions as it comes to blocking calls?
 

Latest posts

Back
Top Bottom