Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OK to do heavy processing in async callbacks?

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?

like image 699
Roman Starkov Avatar asked Jan 11 '11 03:01

Roman Starkov


2 Answers

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.

like image 151
BonanzaDriver Avatar answered Oct 16 '22 06:10

BonanzaDriver


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
like image 30
Sander Avatar answered Oct 16 '22 08:10

Sander