我认为这是一个非常常见的线程场景:
如果你认为每个作业在他完成时都会触发一个事件(我正在使用BackgroundWorker类),我可以想出几种方法来解决它,但我不确定“正确”是什么解决方案是。我希望你们中的一些大师可以指出我正确的方向。
解决方案1:拥有 while(continue){Threading.Sleep(1000);在我的Main()函数中循环。当 A)时,Job_Completed事件处理程序中的代码将设置为continue = false,而没有任何作业要排队, B)所有排队的作业都已完成。我之前和之前都使用过这个解决方案似乎工作正常......对我来说似乎有些“奇怪”。
解决方案2:在我的Main()函数中使用Application.Run()。同样,当 A)时,Job_Completed事件处理程序中的代码将调用Application.Exit(),而且 B 所有排队的作业都已完成。
解决方案3:使用ThreadPool,排队所有500-1000个请求,让它们一次运行10个(SetMaxThreads)并以某种方式等待它们全部完成。
在所有这些解决方案中,基本思想是每次完成另一项工作时都会启动新工作,直到没有工作。因此,问题不仅在于等待现有作业完成,而且还要等到没有任何待处理作业开始。如果ThreadPool是正确的解决方案,那么等待ThreadPool完成所有排队项目的正确方法是什么?
我认为我最重要的困惑是我不明白我的Main()函数中是否能触发事件。显然他们这样做,我只是从Windows消息循环的角度来理解它的机制。解决这个问题的正确方法是什么?为什么?
答案 0 :(得分:3)
即使其他答案很好,如果你想要另一种选择(你可能永远都没有足够的选择),那么这就是一个想法。
只需将每个作业的数据放入一个FIFO堆栈中的结构中。
创建15个帖子。
每个线程都会从堆栈中获取下一个作业,将其弹出。
当线程完成处理时,获取下一个作业,如果堆栈为空,则线程死亡或只是休眠,等待。
唯一的复杂性,解决起来非常简单,就是让弹出窗口处于关键部分(同步读取/弹出)。
答案 1 :(得分:2)
回复:“不知怎的,等待他们全部完成”
ManualResetEvent是你的朋友,在你开始你的大批量创建其中一只小狗之前,在主线程中等待它,在作业完成后在后台操作结束时设置它。
另一种选择是手动创建线程并执行foreach线程thread.Join()
你可以使用它(我在测试期间使用它)
private void Repeat(int times, int asyncThreads, Action action, Action done) {
if (asyncThreads > 0) {
var threads = new List<Thread>();
for (int i = 0; i < asyncThreads; i++) {
int iterations = times / asyncThreads;
if (i == 0) {
iterations += times % asyncThreads;
}
Thread thread = new Thread(new ThreadStart(() => Repeat(iterations, 0, action, null)));
thread.Start();
threads.Add(thread);
}
foreach (var thread in threads) {
thread.Join();
}
} else {
for (int i = 0; i < times; i++) {
action();
}
}
if (done != null) {
done();
}
}
用法:
// Do something 100 times in 15 background threads, wait for them all to finish.
Repeat(100, 15, DoSomething, null)
答案 2 :(得分:1)
我只想使用任务并行库。
你可以将这个作为一个简单的Parallel.For循环来完成你的任务,并且它会自动地干净地管理它。如果您不能等待C#4和Microsoft的实现,那么临时解决方法就是编译并使用Mono Implementation of TPL。 (我个人更喜欢MS实现,特别是较新的beta版本,但Mono one今天功能齐全,可再发行。)
答案 3 :(得分:1)
在线程队列中排队工作项时,应该返回等待句柄。将它们全部放在一个数组中,您可以将它作为参数传递给WaitAll()
函数。
答案 4 :(得分:1)
我会使用ThreadPool。
在开始运行作业之前,请创建一个ManualResetEvent和一个int计数器。将每个作业添加到ThreadPool,每次递增计数器。
在每个作业结束时,递减计数器,当它达到零时,在事件上调用Set()。
在主线程中,调用WaitOne()等待所有作业完成。
答案 5 :(得分:0)
以下是我将如何处理它的伪代码(这不利用ThreadPool,所以有人可能有更好的答案:)
main
{
create queue of 100 jobs
create new array of 15 threads
start threads, passing each the job queue
do whatever until threads are done
}
thread(queue)
{
while(queue isn't empty)
{
lock(queue) { if queue still isn't empty dequeue a thing }
process the thing
}
queue is empty so exit thread
}
编辑:如果您的问题是如何判断线程何时完成,并且您正在使用普通的C#线程(而不是ThreadPooled线程),您可以在每个线程上调用Thread.Join()并使用可选的超时,它将会只有在线程完成后才返回。如果你想跟踪多少线程没有被挂起,你可以用这样的方式循环它们:
for(int i = 0; allThreads.Count > 0; i++)
{
var thisThread = allThreads[i % threads.Count];
if(thisThread.Join(timeout)) // something low, maybe 100 ms or something
allThreads.Remove(thisThread);
}
答案 6 :(得分:0)
ThreadPool
可能是要走的路。 SetMaxThreads
方法可以限制正在执行的线程数。但是,这会限制进程/ AppDomain的最大线程数。如果进程作为服务运行,我建议不要使用SetMaxThreads
。
private static ManualResetEvent manual = new ManualResetEvent(false);
private static int count = 0;
public void RunJobs( List<JobState> states )
{
ThreadPool.SetMaxThreads( 15, 15 );
foreach( var state in states )
{
Interlocked.Increment( count );
ThreadPool.QueueUserWorkItem( Job, state );
}
manual.WaitOne();
}
private static void Job( object state )
{
// run job
Interlocked.Decrement( count );
if( Interlocked.Read( count ) == 0 ) manual.Set();
}
答案 7 :(得分:0)
微软的Reactive Framework非常出色:
Action[] jobs = new Action[100];
var subscription =
jobs
.ToObservable()
.Select(job => Observable.Start(job))
.Merge(15)
.Subscribe(
x => Console.WriteLine("Job Done."),
() => Console.WriteLine("All Jobs Done."))
完成。
Just NuGet&#34; System.Reactive&#34;。