在执行另一个进程之前是否有更好的方法等待排队的线程?
目前我在做:
this.workerLocker = new object(); // Global variable
this.RunningWorkers = arrayStrings.Length; // Global variable
// Initiate process
foreach (string someString in arrayStrings)
{
ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
Thread.Sleep(100);
}
// Waiting execution for all queued threads
lock (this.workerLocker) // Global variable (object)
{
while (this.RunningWorkers > 0)
{
Monitor.Wait(this.workerLocker);
}
}
// Do anything else
Console.WriteLine("END");
// Method DoSomething() definition
public void DoSomething(object data)
{
// Do a slow process...
.
.
.
lock (this.workerLocker)
{
this.RunningWorkers--;
Monitor.Pulse(this.workerLocker);
}
}
答案 0 :(得分:14)
您可能想看一下AutoResetEvent和ManualResetEvent。
这些就是针对这种情况(在做“某事”之前等待ThreadPool线程完成)。
你会做这样的事情:
static void Main(string[] args)
{
List<ManualResetEvent> resetEvents = new List<ManualResetEvent>();
foreach (var x in Enumerable.Range(1, WORKER_COUNT))
{
ManualResetEvent resetEvent = new ManualResetEvent();
ThreadPool.QueueUserWorkItem(DoSomething, resetEvent);
resetEvents.Add(resetEvent);
}
// wait for all ManualResetEvents
WaitHandle.WaitAll(resetEvents.ToArray()); // You probably want to use an array instead of a List, a list was just easier for the example :-)
}
public static void DoSomething(object data)
{
ManualResetEvent resetEvent = data as ManualResetEvent;
// Do something
resetEvent.Set();
}
编辑:忘记提及你可以等待单个线程,任何线程等等。 另外,根据您的情况,AutoResetEvent可以简化一些事情,因为它(顾名思义)可以自动发出事件信号: - )
答案 1 :(得分:13)
仅使用Fork
; {p
Join
和Monitor
怎么样?
Forker p = new Forker();
foreach (var obj in collection)
{
var tmp = obj;
p.Fork(delegate { DoSomeWork(tmp); });
}
p.Join();
此earlier answer上显示的完整代码。
或者对于上限大小的生产者/消费者队列(线程安全等),here。
答案 2 :(得分:4)
除了Barrier之外,Henk Holterman指出(BTW他是非常错误使用Barrier,请参阅我对他的回答的评论),.NET 4.0提供了大量其他选项(至在.NET 3.5中使用它们,您需要下载an extra DLL from Microsoft)。我写了一篇帖子that lists them all,但我最喜欢的是Parallel.ForEach:
Parallel.ForEach<string>(arrayStrings, someString =>
{
DoSomething(someString);
});
在幕后,Parallel.ForEach排队到新的和改进的线程池,并等待所有线程完成。
答案 3 :(得分:3)
当我必须等待任务完成时,我真的很喜欢Begin- End- Async Pattern。
我建议你将BeginEnd包装在一个worker类中:
public class StringWorker
{
private string m_someString;
private IAsyncResult m_result;
private Action DoSomethingDelegate;
public StringWorker(string someString)
{
DoSomethingDelegate = DoSomething;
}
private void DoSomething()
{
throw new NotImplementedException();
}
public IAsyncResult BeginDoSomething()
{
if (m_result != null) { throw new InvalidOperationException(); }
m_result = DoSomethingDelegate.BeginInvoke(null, null);
return m_result;
}
public void EndDoSomething()
{
DoSomethingDelegate.EndInvoke(m_result);
}
}
要开始使用此代码段,请执行此操作:
List<StringWorker> workers = new List<StringWorker>();
foreach (var someString in arrayStrings)
{
StringWorker worker = new StringWorker(someString);
worker.BeginDoSomething();
workers.Add(worker);
}
foreach (var worker in workers)
{
worker.EndDoSomething();
}
Console.WriteLine("END");
就是这样。
旁注:如果您想从BeginEnd返回结果,请将“Action”更改为Func并更改EndDoSomething以返回类型。
public class StringWorker
{
private string m_someString;
private IAsyncResult m_result;
private Func<string> DoSomethingDelegate;
public StringWorker(string someString)
{
DoSomethingDelegate = DoSomething;
}
private string DoSomething()
{
throw new NotImplementedException();
}
public IAsyncResult BeginDoSomething()
{
if (m_result != null) { throw new InvalidOperationException(); }
m_result = DoSomethingDelegate.BeginInvoke(null, null);
return m_result;
}
public string EndDoSomething()
{
return DoSomethingDelegate.EndInvoke(m_result);
}
}
答案 4 :(得分:3)
是的,有。
1)计数器和等待句柄
int ActiveCount = 1; // 1 (!) is important
EventWaitHandle ewhAllDone = new EventWaitHandle(false, ResetMode.Manual);
2)添加循环
foreach (string someString in arrayStrings)
{
Interlocked.Increment(ref ActiveCount);
ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
// Thread.Sleep(100); // you really need this sleep ?
}
PostActionCheck();
ewhAllDone.Wait();
3)DoSomething
应该看起来像
{
try
{
// some long executing code
}
finally
{
// ....
PostActionCheck();
}
}
4)其中PostActionCheck
是
void PostActionCheck()
{
if (Interlocked.Decrement(ref ActiveCount) == 0)
ewhAllDone.Set();
}
ActiveCount初始化为1
,然后递增n
次。
PostActionCheck
被称为n + 1
次。最后一个将触发事件。
此解决方案的好处是使用单个内核对象(这是一个事件),以及轻量级API的2 * n + 1
调用。 (可以减少吗?)
我在这里写了代码,我可能拼错了一些类名。
答案 5 :(得分:2)
.NET 4.0有一个新类,Barrier。
除此之外,您的方法并不是那么糟糕,如果在减量后RunningWorkers为0,您可以仅通过Pulsing进行优化。那看起来像是:
this.workerLocker = new object(); // Global variable
this.RunningWorkers = arrayStrings.Length; // Global variable
foreach (string someString in arrayStrings)
{
ThreadPool.QueueUserWorkItem(this.DoSomething, someString);
//Thread.Sleep(100);
}
// Waiting execution for all queued threads
Monitor.Wait(this.workerLocker);
// Method DoSomething() definition
public void DoSomething(object data)
{
// Do a slow process...
// ...
lock (this.workerLocker)
{
this.RunningWorkers--;
if (this.RunningWorkers == 0)
Monitor.Pulse(this.workerLocker);
}
}
您可以使用EventWaithandle或AutoResetEvent,但它们都包含在Win32 API中。由于Monitor类是纯托管代码,因此我更倾向于监视器。
答案 6 :(得分:0)
我不确定确实存在,我最近做了类似扫描子网的每个IP以接受特定端口的事情。
我可以提出一些可以提高性能的事情:
使用ThreadPool的SetMaxThreads方法来调整性能(即平衡有大量线程一次运行,而不是在如此大量的线程上锁定时间)。
在设置所有工作项时不要睡觉,没有真正的需要(我立刻意识到。但是在DoSomething方法中睡觉,可能只有一毫秒,以允许其他线程如果需要,可以跳进去。
我确信您可以自己实现更加自定义的方法,但我怀疑它比使用ThreadPool更有效。
P.S我不是100%清楚使用显示器的原因,因为你还在锁定?请注意,问题只是因为我之前没有使用过Monitor类,而不是因为我实际上怀疑它是否正在使用。
答案 7 :(得分:0)
使用Spring Threading。它内置了Barrier实现。