Sometimes you realize just how long you've been away from the kind of programming you used to do when you suddenly discover new blind spots in your understanding.
I haven't written C# code for work for at least 3 years. (I have been doing other kinds of software engineering since then, such as frontend and lately, data engineering.) But my current personal programming project that I wrote about here, is in C#. For about 10 years now C# had async and await keywords, letting you easily write code handling asynchronous operations. They enable you to start an asynchronous operation and allow the rest of the program to execute the parts that don't need the result of this operation. Then, when the operation completes, the program will execute the code that does something with the result.
Typically when you want your code to call a system outside of your code's boundaries, especially one that communicates over the network or with the file system, you should call it asynchronously. Examples would be databases or other services that run on their own ports.
In the intervening years, my understanding of async / await code (which, to begin with, was "workable" but not deep), deteriorated to plain wrong. I was left with a notion that if I'm not waiting for the asynchronous operation to return a result, then I don't need to invoke it asynchronously. Which was patently wrong. I only realized it when I called DynamoDB synchronously to insert a bunch of records, and was surprised that only half of them got inserted.
At first I thought that maybe there was something wrong with half of my data and DynamoDB was rejecting those records. I also wrote here about how with the object persistence model DynamoDB won't give an error message if it rejects your records that won't meet certain criteria. It fails silently. So I thought that was also the case here. But the records it inserted were no different in their shape or form than the records it rejected. So I was puzzled.
And then finally some long-forgotten memory started to flicker in my subconscious, and I started to wonder if my program executes and quits before it could insert all the records, because it is not awaiting their insertion. Which is exactly what would happen if you invoked an asynchronous operation without await. The operation is still asynchronous (I don't control that part, it's in a third-party module), but if I'm invoking it without an await, it starts executing but might not finish, because the rest of my program finishes executing and quits. So this has nothing to do with wanting a return value from your asynchronous operation. The await keyword is necessary so it could have a chance to finish.
Back when I was writing a lot of C# and Javascript code, I would run into this problem once in a while with unit tests. Sometimes unit tests would fail intermittently because an asynchronous operation in one of the tests sometimes didn't have time to finish before the execution of the whole test suite was completed. That was because it was not awaited. (More often, the reverse happened: if the asynchronous operation completed and the callback was executed, the test would fail; if it wasn't, it didn't fail, giving a false illusion of success.) But all of this managed to evaporate from my memory in 3 years of not using C#. Thankfully, it had not evaporated so thoroughly as not to ring some distant bells, which eventually led me to diagnosing the problem.