Из собственного опыта могу посоветовать Completion Ports. Очень удобно, ибо вся синхронизация делается самим ядром, заморачиваться со всякими эвентами не надо. Producer сует в порт очередной айтем с помощью PostQueuedCompletionStatus, а соответсвенно Consumer выгребает это дело с помощью GetQueueCompletionStatus. На Висте и выше можно заюзать GetQueuedCompletionStatusEx которая позволяет не один айтем, а сразу кучу выгрести за один вызов. Получается очень "чистенько" в плане кода, и насколько можно производительно.