OK to do heavy processing in async callbacks?
Asked Answered
S

2

7

Is it OK to do heavy processing in .NET's asynchronous callbacks, hogging them for multiple seconds before returning? Or am I depriving the OS / the runtime of important resources?

For example, consider TcpListener.BeginAcceptSocket. My callback starts off by invoking EndAcceptSocket, then spends a while receiving data, and only then closes the socket and returns. Is this how it was meant to be used, or am I expected to do the extra processing on my own thread?

Straitjacket answered 11/1, 2011 at 3:20 Comment(2)
I would urge you to take a look at Rx (Reactive extensions) which makes asynchronous programming a beautiful evening stroll in a park! msdn.microsoft.com/en-us/devlabs/ee794896.aspxZacheryzack
Some useful Rx links : rxwiki.wikidot.com Here is little code I wrote to asynchronously update a list box everytime I type in a textbox, and throttle it by 0.5 secs. See-> #4656119Zacheryzack
J
1

Yes, this is how asynchronous Sockets (Tcp client, listeners, etc.) are designed for use. You should always ensure that you invoke the end aysnc method and then do whatever processing you desire. Not invoking the EndAccept(), EndSEnd(), EndReceive(), etc., etc. methods leaves you potentially open to a memory leak, so it's always a good practice to follow.

The threads that are used are no different than if you had manually spooled up a background thread yourself, and are infact desinged to be used for even "long term operations" of a couple of seconds. Trust me, you don't want anything that takes that long to be running on a dispatching or GUI thread.

I have over 90 mobile based systems which use asychronous Sockets for commucation to a Server and it does an excellent job: much faster than web services (remember all web protocols run on top of a Socket anyway), easy to detect errors, etc., etc.

I do the same on my Server code (mixed in with some other WCF for middleware and backend communication pieces) and it's the most scaleable comm that we've used. You'll have to do a Google on it but there's a guy who published his testing using this technology and he was able to support 1,000 concurrent communications to a server with just 11 threads. Not bad.

Server example from MS: http://msdn.microsoft.com/en-us/library/fx6588te.aspx

Client example from MS: http://msdn.microsoft.com/en-us/library/bew39x2a.aspx

It takes more than these to get it "perfected" but this is a good place to start.

Jeep answered 11/1, 2011 at 3:33 Comment(5)
The article at codeproject.com/KB/threads/threadtests.aspx seems to indicate that the .NET ThreadPool has performance problems if you do long processing in thread pool threads (such as asynchronous call completion handlers). Has this problem not been visible for you?Melon
I can't really speak to truely long processing times, but from a Socket communications perspective we've never had any problems. All our communications follow a buffered asynch approach, and even large transmission ... say a gig of data, which on a mobile platform can take a long time, as long as the network is solid the Sockets have proven reliable. Ironically just about every communication networking technology uses Sockets underneath a higher abstraction to handle the heavy lifting (usually not available in mobile, though).Jeep
@Sander: Back in 2003 the ThreadPool had a default of 25 threads per cpu in it (and most computers were single core). Now the default is 250 per cpu so there are less problems than there used to be: msdn.microsoft.com/en-us/library/… (and .net 4 takes it up to 1023 per cpu on 32 bit and 32768 per cpu on 64 bit!)Teammate
@Matthew: still, those are just max numbers - the thread pool takes time to grow. I repeated the experiment in the CodeProject article and did notice an issue in such a scenario. I'll post my results as an answer in this question.Melon
@Sander: according to "Parallel Programming with Microsoft.NET" which was authored by Campbell, Johnson, Miller, and Toub the ThreadPool's thread injection algorithm adds additional threads at the rate of 2 per second. I’m not sure if that’s adjustable or the same value that would be used from the communication APIs but interesting to know none the less. I have a Server that uses my communication engine, based on Asych Sockets, and it does a great job of handling 2,200 concurrent users. I’m currently implementing a 50,000 user system (dual quad core box on 64-bit OS).Jeep
M
1

I repeated the experiments of a CodeProject article on this topic and found the results for .NET 4 to be similar to what were described in 2003. Note that the article did not actually list the results for the problematic section but as I understand it the main issue still exists.

I reused the code from the CodeProject article - just download it to run this test yourself or to experiment.

The test will try to use 10 parallel threads to count as high as it can in 1 second.

Using 10 background threads (i.e. new Thread())

T0 = 4451756
T1 = 4215159
T2 = 5449189
T3 = 6244135
T4 = 3297895
T5 = 5302370
T6 = 5256763
T7 = 3779166
T8 = 6309599
T9 = 6236041
Total = 50542073

Using 10 ThreadPool work items

T0 = 23335890
T1 = 20998989
T2 = 22920781
T3 = 9802624
T4 = 0
T5 = 0
T6 = 0
T7 = 0
T8 = 0
T9 = 0
Total = 77058284

Note that only 4 thread pool work items out of 10 ever actually executed during the 1 second time slice! This is on a quad-core CPU, so that was one thread per core. The other tasks executed after the first four completed and because the 1 second allotted had already expired, they did not increment their counters.

The conclusion here: with long tasks, ThreadPool will make some tasks wait behind others! Thus, I would strongly recommend against doing any long processing in ThreadPool tasks (such as asynchronous completion handlers). Otherwise, you might keep more important asynchronous calls from completing if your data processing is hogging the CPU, or you might have very unstable performance if only some tasks do much processing.

Using custom ThreadPool implementation from article

T0 = 7175934
T1 = 6983639
T2 = 5306292
T3 = 5078502
T4 = 3279956
T5 = 8116320
T6 = 3262403
T7 = 7678457
T8 = 8946761
T9 = 8500619
Total = 64328883
Melon answered 14/1, 2011 at 6:12 Comment(2)
What you've missed from your results is that the ThreadPool version performed significantly better than your non-threadpool version by around 50%! The article was saying that using the ThreadPool was significantly slower but that's not the case anymore. As with everything in programming though it comes down to "it depends". If your tasks don't need to start executing immediately then you can use the ThreadPool and they'll finish faster due to less context switching, otherwise you can manage it yourself.Teammate
@Matthew: Yes, I agree that ThreadPool counted more here but I disagree about the reason. The reason that ThreadPool counts more is that the four ThreadPool threads already exist, whereas with new threads manually created, it takes a significant amount of time for the thread to actually start. So I would reverse your conclusion: if your tasks don't need to execute immediately, use a new Thread; if you need to execute immediately, use ThreadPool (and don't hog it with long tasks).Melon

© 2022 - 2024 — McMap. All rights reserved.