我是多线程概念的新手。我需要将一定数量的字符串添加到队列中并使用多个线程处理它们。使用线程安全的ConcurrentQueue
。
这是我尝试过的。但是不会处理添加到并发队列中的所有项目。只处理前4个项目。
class Program
{
ConcurrentQueue<string> iQ = new ConcurrentQueue<string>();
static void Main(string[] args)
{
new Program().run();
}
void run()
{
int threadCount = 4;
Task[] workers = new Task[threadCount];
for (int i = 0; i < threadCount; ++i)
{
int workerId = i;
Task task = new Task(() => worker(workerId));
workers[i] = task;
task.Start();
}
for (int i = 0; i < 100; i++)
{
iQ.Enqueue("Item" + i);
}
Task.WaitAll(workers);
Console.WriteLine("Done.");
Console.ReadLine();
}
void worker(int workerId)
{
Console.WriteLine("Worker {0} is starting.", workerId);
string op;
if(iQ.TryDequeue(out op))
{
Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
}
Console.WriteLine("Worker {0} is stopping.", workerId);
}
}
答案 0 :(得分:5)
您的实施存在一些问题。第一个也是显而易见的是status = 0
方法只将零个或一个项目出列然后停止:
worker
应该是:
if(iQ.TryDequeue(out op))
{
Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
}
然而,这不足以使您的程序正常运行。如果你的工人出列的速度比主线程入队的速度快,那么当主要任务仍然排队时它们会停止。你需要告诉工人他们可以停下来。您可以定义一个布尔变量,一旦排队完成,将设置为 while(iQ.TryDequeue(out op))
{
Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
}
:
true
工人将检查价值:
for (int i = 0; i < 100; i++)
{
iQ.Enqueue("Item" + i);
}
Volatile.Write(ref doneEnqueueing, true);
答案 1 :(得分:3)
您的工作人员从queue
中取出一件物品,然后完成工作,让它们工作直到queue
为空。
使用if
while
void worker(int workerId)
{
Console.WriteLine("Worker {0} is starting.", workerId);
string op;
while (iQ.TryDequeue(out op))
{
Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
}
Console.WriteLine("Worker {0} is stopping.", workerId);
}
正如您将运行它,您将看到,所有物品附近将由两名工人处理。原因:你的cpu有两个内核,两个都工作,没有“free tiem slot”来创建新任务。如果你想要处理所有4个任务来处理项目,你可以添加一个延迟,让你的处理器有时间来创建一些任务,例如:
while (iQ.TryDequeue(out op))
{
Console.WriteLine("Worker {0} is processing item {1}", workerId, op);
Task.Delay(TimeSpan.FromMilliseconds(1)).Wait();
}
为您提供所需的输出:
...
Worker 0 is processing item Item8
Worker 1 is processing item Item9
Worker 2 is processing item Item10
Worker 3 is processing item Item11
Worker 3 is processing item Item13
Worker 1 is processing item Item12
...
答案 2 :(得分:0)
我最近实际上一直在与ConcurrentQueue
一起工作,并认为我会分享这一点。我创建了一个名为ConcurrentQueue
的自定义CQItems
,它具有使用给定参数构建自身的方法。在内部,当您告诉它构建 x 个 y 个项目时,它将对项目构造函数进行Parallel.For
调用。这样做的好处是,当方法或函数调用CQItems myCQ = CQItems.Generate(x, y)
时,该调用来自基本应用程序线程,这意味着在完成队列构建之前,任何人都无法查看队列。但是在队列类内部,它是使用线程构建的,并且比仅使用List<>
或Queue<>
快得多。通常,它是凭空产生的,但有时(基于参数)是根据SQL创建项目的-基本上是基于现有数据生成对象。无论如何,这些是CQItems
类中的两个方法可以帮助解决这个问题:
public void Generate(int numberOfItems = 1, ItemOptions options = ItemOptions.NONE)
{
try
{
Type t = typeof(T);
if (t == typeof(Item))
throw new ArgumentException("Type " + t + " is not valid for generation. Please contact QA.");
else
Parallel.For(0, numberOfItems, (i, loopState) =>
{
try
{
GenerateItemByType(typeof(T), options);
}
catch
{
loopState.Stop();
throw;
}
});
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
if (x is SQLNullResultsException)
{
throw x;
}
else if (x is ImageNotTIFFException)
{
throw x;
}
else
{
throw x;
}
return true;
});
}
catch
{
throw;
}
finally
{
ItemManager.Instance.Clear();
}
}
private void GenerateItemByType(Type t, ItemOptions options = ItemOptions.NONE)
{
try
{
if (t == typeof(ItemA))
{
if ((options & ItemOptions.DUPLICATE) != 0)
{
this.Enqueue(new ItemB(options));
}
else
{
this.Enqueue(new ItemA(options));
}
}
else if (t == typeof(ItemC))
{
this.Enqueue(new ItemC(options));
}
}
catch
{
throw;
}
finally { }
}
一些有用的注释:
在loopState
中提供Parallel.For()
变量使我们可以在捕获到异常的情况下将状态设置为 stop 。这很不错,因为如果您的循环被要求执行1000次操作,并且第5次迭代引发异常,它将继续循环。您可能希望这样做,但是在我的情况下,需要退出线程循环。您仍然会得到一个AggregateException
(显然,线程抛出异常时会发生这种情况)。解析这些内容并只发送第一个例外,可以节省大量时间和头痛,因为他们试图通过一个庞大的例外组进行除草,因为后来的例外可能(或可能没有)由于第一个例外而引起。
对于重新抛出,即使我打算无论如何都将它们抛出堆栈,我还是尝试为大多数期望的异常类型添加catch语句。其中一部分是用于故障排除(能够轻松解决特定的异常情况)。部分原因是因为有时我希望能够做其他事情,例如停止循环,更改或添加异常消息,或者在分解AggregateException
的情况下,仅发送一个异常备份堆栈而不是整个集合。对于可能正在查看此内容的任何人来说,这只是一个澄清点。
最后,以防万一,Type(T)
值来自我的CQItems
类本身:
public class CQItems<T> : ConcurrentQueue<Item>, IDisposable