我有以下代码,我从源填充用户,为例子如下所示。我想要做的是使用多个消费者的BlockingCollection。
低于正确的方法吗?还有什么是最好的线程数?好吧这取决于硬件,内存等。或者我怎样才能以更好的方式做到这一点?
下面的实现也会确保我将处理集合中的所有内容,直到它为空?
class Program
{
public static readonly BlockingCollection<User> users = new BlockingCollection<User>();
static void Main(string[] args)
{
for (int i = 0; i < 100000; i++)
{
var u = new User {Id = i, Name = "user " + i};
users.Add(u);
}
Run();
}
static void Run()
{
for (int i = 0; i < 100; i++)
{
Task.Factory.StartNew(Process, TaskCreationOptions.LongRunning);
}
}
static void Process()
{
foreach (var user in users.GetConsumingEnumerable())
{
Console.WriteLine(user.Id);
}
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
答案 0 :(得分:5)
一些小事
users.CompleteAdding()
循环后执行for
来解决此问题。Run()
将启动你的100个线程(除非你的真实过程涉及大量等待无争议的资源,否则可能会过多)。由于任务不是前台线程,因此当您Main
退出时,它们不会保持您的程序处于打开状态。您需要CountdownEvent来跟踪所有内容的完成情况。这是带有修复程序的代码的更新版本
class Program
{
private const int MaxThreads = 100; //way to high for this example.
private static readonly CountdownEvent cde = new CountdownEvent(MaxThreads);
public static readonly BlockingCollection<User> users = new BlockingCollection<User>();
static void Main(string[] args)
{
Run();
for (int i = 0; i < 100000; i++)
{
var u = new User {Id = i, Name = "user " + i};
users.Add(u);
}
users.CompleteAdding();
cde.Wait();
}
static void Run()
{
for (int i = 0; i < MaxThreads; i++)
{
Task.Factory.StartNew(Process, TaskCreationOptions.LongRunning);
}
}
static void Process()
{
foreach (var user in users.GetConsumingEnumerable())
{
Console.WriteLine(user.Id);
}
cde.Signal();
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
对于我之前说过的“最佳线程数”,这实际上取决于你在等什么。
如果您正在处理的是CPU绑定,则最佳线程数可能为Enviorment.ProcessorCount。
如果您正在等待外部资源,但新请求不会影响旧请求(例如,询问20个不同的服务器以获取信息,服务器上的服务器n
上的负载不会影响服务器上的负载n+1
)在这种情况下,我会让Parallel.ForEach为您选择线程数。
如果您正在等待争用的资源(例如读/写硬盘),您将不想使用很多线程(甚至可能只使用一个)。我刚刚发布了a answer in another question关于这一点,当从硬盘读入时,你应该一次只使用一个线程,这样硬盘驱动器就不会一遍遍地试图完成所有的读取。