Producer-Consumer
The producer-consumer pattern is very common to distribute work between
threads where data is passed from producing threads to consuming threads
without the need for sharing or locking. .NET has very rich support for this,
but at the most basic level, System.Collections.Concurrent
provides the BlockingCollection
as shown in the next example in C#:
using System;
using System.Threading;
using System.Collections.Concurrent;
var messages = new BlockingCollection<string>();
var producer = new Thread(() =>
{
for (var n = 1; i < 10; i++)
messages.Add($"Message #{n}");
messages.CompleteAdding();
});
producer.Start();
// main thread is the consumer here
foreach (var message in messages.GetConsumingEnumerable())
Console.WriteLine(message);
producer.Join();
The same can be done in Rust using channels. The standard library primarily
provides mpsc::channel
, which is a channel that supports multiple producers
and a single consumer. A rough translation of the above C# example in Rust
would look as follows:
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
let (tx, rx) = mpsc::channel();
let producer = thread::spawn(move || {
for n in 1..10 {
tx.send(format!("Message #{}", n)).unwrap();
}
});
// main thread is the consumer here
for received in rx {
println!("{}", received);
}
producer.join().unwrap();
}
Like channels in Rust, .NET also offers channels in the
System.Threading.Channels
namespace, but it is primarily designed to be used
with tasks and asynchronous programming using async
and await
. The
equivalent of the async-friendly channels in the Rust space is offered by the
Tokio runtime.