2

When looking for an elegant way to asynchronously receive a stream of datagrams from a UDP socket, I came across this question: How to use asynchronous Receive for UdpClient in a loop?

The advantages of the first answer I understand, as they use the familiar BeginReceive/EndReceive methods. This solution is nice in that there are no threads hanging around being blocked.

The second answer, however gives two different Task-ish solutions, one using ReceiveAsync and the other using the synchronous Receive method. I'm wondering, in this case, what the advantage is. As I understand it, even in the ReceiveAsync case, there's still a (threadpool?) thread waiting around for things to happen.

Is there an advantage in using Async methods in this case? If not, is there a way to implement this type of pattern using Async methods without the overhead of a thread, task, or other blocking object?

Community
  • 1
  • 1
Mark
  • 11,257
  • 11
  • 61
  • 97
  • 4
    `As I understand it, even in the ReceiveAsync case, there's still a (threadpool?) thread waiting around for things to happen.` The whole point of async is that that is not true. Read http://blog.slaks.net/2014-12-23/parallelism-async-threading-explained/ – SLaks Feb 23 '15 at 22:02
  • In particular, the `*Async()` methods are just friendlier wrappers around the `Begin*()` methods. – SLaks Feb 23 '15 at 22:03
  • 2
    @SLaks See the linked post; it is in fact unnecessarly allocating a thread pool thread to do some work, although not quite as much as the OP thinks. – Servy Feb 23 '15 at 22:03
  • 1
    @Servy: That's a good point; his `Task.Run()` call is completely useless. – SLaks Feb 23 '15 at 22:04
  • 2
    You are getting a rosy picture from that 2nd answer. He intentionally doesn't do anything with the data that's received and does not deal with any errors. Once you flesh that in you'll definitely see the difference. Those details get pretty nasty in a hurry with APM. – Hans Passant Feb 23 '15 at 23:03

2 Answers2

3

First of all, in a naturally asynchronous API there are no blocked threads in both the async-await (TAP) version and Begin/End(APM) (There Is No Thread).

In this specific case however, the linked answer does waste a ThreadPool thread unnecessarily and this should be avoided.

The ReceiveAsync case offloads the synchronous part of the operation (until await ReceiveAsync) to a ThreadPool thread. This is useful if you have a substantive CPU-bound operation to perform and you want to free up the calling thread. This doesn't seem to be case as there's nothing to do other than create the client.

You can simply remove the Task.Run and have an async method:

async Task ListenAsync(int port, CancellationToken token)
{
    using (var client = new UdpClient(port))
    {
        while (true)
        {
            var result = await client.ReceiveAsync().WithCancellation(token).ConfigureAwait(false);
            // process result.Buffer
        }
    } 
}
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Yes, in a naturally asynchronous API there is no thread, but in the example that the question specifically mentions it is not a proper implementation, it *does* unnecessarily create thread pool thread, the use of `FromAsync` isn't necessary, as there is already a `Task` returning async method, and your comments on the TPL being preferable as a paradigm are rather offtopic in the face of the question, which is asking about properties of a specific implementation *using that paradigm*. – Servy Feb 23 '15 at 22:12
  • @Servy The OP asked (specifically in the title) about advantages of TAP over APM and different TAP versions. This answer cover the general case and the specific case. – i3arnon Feb 23 '15 at 22:21
  • So you read the title and none of the body of the question? The body of the quesiton makes it very clear that it's not asking about the difference between different approaches to asynchronous methods. – Servy Feb 24 '15 at 14:58
  • @Servy I've read **both**. And answered **both**. Which is crucial since this post would be the end result of future Google searches. And *"even in the `ReceiveAsync` case, there's still a (threadpool?) thread waiting around for things to happen."* makes that a lot less clear than you make it seem. – i3arnon Feb 24 '15 at 15:33
  • So it's important to ask a question that the OP isn't actually asking, because future readers might make the same mistake that you did and believe that the OP is asking something completely different? If someone finds a question that seems similar but is in fact asking about something different than what they're asking about, then they can't really expect to get the answer to their question. You cannot answer everything about everything in the event that someone wanting to know something else just happens to find the question. – Servy Feb 24 '15 at 15:40
  • @Servy I don't think the OP is really **only** concerned about that specific issue when the title is irrelevant and they mention waiting `ThreadPool` threads. If you're so sure about that then I suggest you edit these issue out of the question. I'm not **that** sure and so instead of assuming one way or another I gave the exact literal answer and the more general scope in which it stands. – i3arnon Feb 24 '15 at 15:51
  • If the OP had no idea that a properly asynchronous implementation wouldn't involve thread pool threads waiting on the operations then why would be asking if the example that he saw actually needs to create a thread pool thread and asks if it is indeed causing a thread pool thread to wait for the operation? If he didn't know that async IO didn't need to involve thread pool threads waiting, he wouldn't have asked the question he did in the first place. You're also burying the actual answer to the question (which you edited in later) inside a bunch of content that he already knows. – Servy Feb 24 '15 at 15:56
  • @Servy The OP may know what's a proper asynchronous implementation but it seems they are asking whether `async-await` is indeed that proper implementation (as opposed to `BeginX/EndX` which they know as a proper implementation). – i3arnon Feb 24 '15 at 15:59
  • No, he's asking if this particularly TPL implementation is implemented correctly, and if not, how *should* this operation be properly implemented in the TPL. He's given no reason to believe that he doesn't understand that the TPL vs. other asynchronous operations are all capable of solving various problem, it's purely a matter of how easy/hard it is to do. – Servy Feb 24 '15 at 16:00
  • @Servy And again, I think that both the title and quote are reasons enough to doubt that. – i3arnon Feb 24 '15 at 16:03
  • The title gives reason to doubt that, the body of the post leaves little doubt. The quote is very much the reason that makes most of your answer irrelevant. The quote indicates that he knows a properly implemented asynchronous solution shouldn't involve tying up a thread pool thread, but the linked post does in fact appear to be doing that (it isn't, but it isn't immediately obvious that that's the case without a through understanding of how `Task.Run` works). That he's bringing up the concern is exactly why you can know that he is aware of most of what you've said here. – Servy Feb 24 '15 at 16:07
  • @Servy is correct; What I'm asking here is, "if the linked example is correct, what's the point of XXXAsync?". The secondary question would be, "if the linked example is incorrect (as it appears to be), what is the correct implementation?" – Mark Feb 24 '15 at 16:33
  • @Mark great. So by explaining the point of `XAsync` I show that the linked example is incorrect. And since it's incorrect I added a correct implementation. Is that too much? – i3arnon Feb 24 '15 at 16:38
  • @i3arnon Your answer is great as far as the question goes, but your implementation doesn't solve the problem in the linked question, which is, "how do I do this in a loop?". Your implementation only receives a single datagram. Either way, you get credit for this one. – Mark Feb 24 '15 at 16:44
  • @Mark true. I've added a `while` and `CancellationToken` (you should also take a look at this if you actually plan on using it: [Async network operations never finish](http://stackoverflow.com/q/21468137/885318)) – i3arnon Feb 24 '15 at 16:48
  • @i3arnon once you put the `while` loop in there, I think you've fallen into the same trap? I.E. you're just holding a thread around which continually awaits something. I think you need a .ContinueWith() in there instead. – Mark Feb 24 '15 at 17:33
  • @Mark Then @Servy wasn't right. You're not holding any threads. You use the calling thread until you reach the `await` the first time. When it completes you get another thread from the `ThreadPool` that executes the contents of the `while` loop until it reaches `await`. The thread is then freed while you asynchronously wait for the receive operation to complete and so forth and so forth. You're never holding a thread while you wait. You only get a thread when the task completes. – i3arnon Feb 24 '15 at 17:38
  • @Mark Logically `ContinueWith` and `await` are not that different. – i3arnon Feb 24 '15 at 17:39
0

Well, it's not allocating a thread pool thread to sit around and wait for the async operation to finish. Rather, it's allocating a thread pool thread to start the asynchronous operation, instead of starting it in the current thread.

There really isn't any reason to do this, at least unless the act of starting the asynchronous operation takes a long time, and it really shouldn't if it has been written correctly.

Can you implement the same thing without starting the async operation in a thread pool thread, sure, just remove the code to start it in a thread pool thread.

Servy
  • 202,030
  • 26
  • 332
  • 449