Questions about asynchronous programming

I was wondering in a rich gui application, what would happen if an awaited method was finished and ready to go back and continue the rest of the code after the await while at the same time a control on the ui was being triggered and another method needed to be ran. What would have priority in that scenario?

Also if I have a method that calls an api and that request/response takes ~250ms and I needed to call this api thousands of times more, could I use asynchronous programming to increase the performance and reduce that time it takes to make those api calls? Could I wrap the api call in a TaskCompletionSource and call it thousands of times in a for loop and do a Task.WhenAll() so that they can be performed all at once instead of synchronously (call api then wait for response then loop to make the next api call)?

11 comments · 86% upvoted · other discussions

comments

Slypenslyde

The answer to the first bit is actually sort of obfuscated from us these days, but was clear in old-school Windows API programming.

The reason the UI thread is special is it runs the "message loop" for the application. That is basically a FIFO queue of delegates that represent what the UI thread should be doing. Everything we see as an event in Windows Forms/WPF is a Windows Message that goes in the queue, with some neat optimizations here and there.

So the answer is "if three things try to happen on the UI thread at the same time, they all end up in the message queue in the order that they arrive. Then they are executed, one at a time, in FIFO order."

This is why a lot of the older mechanisms to send things to the UI thread had both synchronous and asynchronous versions. If your thread couldn't afford to wait on the UI thread if it was burdened, you wanted to use asynchronous mechanisms to marshal. (There were other reasons too, I'm oversimplifying.)

The answer to the second is "maybe". It's very, very difficult to speak generally if that will speed something up.

If what you are doing is I/O bound, like accessing a disk, generally only one thing can read/write the disk at a time, so it doesn't matter if you call it 1,000 times from 1 thread or 1 time from 1,000 threads, it takes t * 1000 to complete.

If what you are doing is compute bound, like "summing sets of integers", then splitting it across multiple threads will work... to a point. The CPU only has so many cores, there are costs to context switching, etc.

All of that also has the shadow of "threads consume resources". If you spin up your own threads, they all consume some memory and that takes time to allocate. If you decide instead to use one of .NET's many thread pools, then you're bound by however many its algorithm decides is "right."

So it takes a lot of careful analysis to know if what you are doing gets faster or slower.

NetFutility [OP]

Wow thanks for the clarification on the first question. A friend asked me about it and I had no idea how to answer them.

On the second question, I am calling a method that performs a number of API calls to Amazon S3 moving objects around the same buckets. I'm not sure if that is inherently a synchronous process that's limited by access to the buckets or objects. I was hoping to make that method asynchronous and perform the same set of actions on each of the thousands of objects at once in the same thread and await them all. Anything sounds better than just going through the list of objects one at a time.

I've tried doing it in parallel with thread pool threads but that did not really reduce the time that much since it is still running the method synchronously but in batches split amongst 3 or 4 threads

Slypenslyde

Yes, this is basically a case someone else pointed out I left out (but in reality I wasn't trying to write an exhaustive guide, just some examples).

It's useful to think of how threading might help by looking at the points where you are stuck waiting. I'm imagining it works like this:

  1. Make a data structure containing information about the moves to make.
  2. Make an API call (over network) to perform the move.
  3. Wait for the API call to complete.
  4. Handle errors/note completion.

Steps 2 and 3 probably spend the most time per item, thus could yield the most savings.

But if Amazon has a rate limit, you have to respect it. And if your network adapter has some limitation that affects this, that impacts you. You probably need to check out how to configure whatever HTTP client you are doing in case it limits simultaneous connections to the same endpoint (for example, most browsers only allow 4 and I think that is a setting in HttpClient.)

So there are a lot of reasons why you might be constrained, even though network I/O like this should be good to do in parallel, but there are a lot of things that might limit how parallel it can be.