Πλησιάζουμε, πλησιάζουμε! 
Το σενάριο είναι πολύ συνηθισμένο σε multithreaded εφαρμογές και έχει και όνομα: Producer/Consumer. Απλά και μόνο ψάχνοντας με αυτά τα keywords θα βρεις πολλές τεχνικές για να το αντιμετωπίσεις.
Στο .NET 4 και άνω υπάρχουν πολλοί τρόπο να το αντιμετωπίσεις και κανένας δεν χρειάζεται lock.
Μία περίπτωση είναι να χρησιμοποιήσεις το BlockingCollection (το οποίο εσωτερικά χρησιμοποιεί το ConcurrentQueue). Το ένα thread γράφει στο collection ενώ το άλλο είτε καλεί την GetConsumingEnumerable() για να διαβάσει, ή καλεί την Take() για να μπλοκάρει μέχρι να γραφτεί κάτι στο collection. Το BlockingCollection εσωτερικά χρησιμοποιεί το ConcurrentQueue αλλά μπορείς να το αντικαταστήσεις με το ConcurrentStack ή οποιοδήποτε άλλο collection υλοποιεί το IProducerConsumerCollection interface και σου δίνει την queueing συμπεριφορά που θέλεις (π.χ. priority queue?)
Και πριν διαμαρτυρηθεί ο Νίκος ο Παλλαδινός, το κύριο μειονέκτημα του BlockingCollection είναι ότι ... μπλοκάρεις όντως ένα thread που περιμένει μέχρι να του δώσεις δεδομένα.
Άλλη λύση, είναι να χρησιμοποιήσεις το ActionBlock του TPL DataFlow. Το ActionBlock δέχεται δεδομένα ως input, τα επεξεργάζεται με ένα ή περισσότερα δικά του threads (χωρίς να μπλοκάρει) και γράφει το αποτέλεσμα σε ένα output buffer. Μπορείς να συνδέσεις blocks μεταξύ τους σε ένα Pipeline και π.χ. το ένα να επεξεργάζεται τα inputs και το επόμενο να αποθηκεύει τα αποτελέσματα στη βάση όπως έρχονται.
Το ωραίο με το DataFlow είναι ότι μπορείς να συνδέσεις πολλά βήματα μεταξύ τους σαν Lego και να έχεις το καθένα να εκτελείται σε ξεχωριστό thread. Αυτό απλουστεύει απίστευτα τον προγραμματισμό του κάθε βήματος καθώς το καθένα είναι απομονωμένο και δεν σε απασχολεί πως θα τα συγχρονίσεις ή πως θα διαχειριστείς τα threads. Μπορείς έτσι να σπάσεις την επεξεργασία σε πολύ περισσότερα κομμάτια απ' ότι με ένα μόνο BlockingCollection
Παναγιώτης Καναβός, Freelancer
Twitter: http://www.twitter.com/pkanavos