我想同时运行多个线程(例如,最多5个线程),当任何一个完成时,新的线程以不同的数据开始。 (一个完成,一个新的开始,两个完成,两个新的开始......)
Main for循环是主要形式,但是从不同的线程运行而不是阻止UI。
当我运行它时,程序会添加5个Web浏览器控件(作为可视化进度),当页面完成加载后,它将删除已加载的控件。
问题是没有更多的控件被添加到表单中
也许信号量没有正确发布以允许新的启动或我错过了其他的东西?
如果我关闭程序,它不会退出,我认为它会在WaitHandle.WaitOne上被阻止,因为还有更多的工作要做。
我删除了一些不需要的数据,以提高代码清晰度。
Semaphore pool = new Semaphore(5, 5);
Scraper[] scraper = new Scraper[5];
Gecko.GeckoWebBrowser wb = null;
int j = 0;
for (int i = 0; i < arrScrapeboxItems.Count; i++)
{
pool.WaitOne();
bool pustiMe = true;
while (pustiMe)
{
if (scraper[j] == null) scraper[j] = new Scraper();
if (scraper[j].tred == null)
{
ScrapeBoxItems sbi = (ScrapeBoxItems)arrScrapeboxItems[i];
doneEvents.Add(new ManualResetEvent(false)); // this is for WaitHandle.WaitAll after the for loop is done all the items
wb = new Gecko.GeckoWebBrowser();
PoolObjects po = new PoolObjects();
po.link = sbi.link;
// etc...
scraper[j].ThreadsCompleted += new Scraper.ThreadsHandler(frmMain_NextThreadItemsCompleted);
scraper[j].tred = new Thread(new ParameterizedThreadStart(scraper[j].Scrape));
scraper[j].tred.Start(po);
pustiMe = false;
if (j == maxThreads - 1)
j = 0;
else
j++;
break;
}
else if (scraper[j].tred.IsAlive) // if the thread is finished, make room for new thread
{
scraper[j] = null;
}
if (pustiMe) Thread.Sleep(1000);
}
}
// event from Scraper class
void frmMain_ThreadsCompleted()
{
pool.Release();
}
Scraper类看起来像:
public void Scrape(object o)
{
po = (PoolObjects)o;
// do stuff with po
po.form.Invoke((MethodInvoker)delegate
{
po.form.Controls.Add(po.wb);
po.wb.DocumentCompleted += new EventHandler<Gecko.Events.GeckoDocumentCompletedEventArgs>(wb_DocumentCompleted);
po.wb.Navigate(po.link);
});
}
void wb_DocumentCompleted(object sender, Gecko.Events.GeckoDocumentCompletedEventArgs e)
{
var br = sender as Gecko.GeckoWebBrowser;
if (br.Url == e.Uri)
{
form.Controls.Remove(po.wb);
ThreadsCompleted();
manualReset.Set();
}
}
答案 0 :(得分:2)
要么你有错字还是大错。
else if (scraper[j].tred.IsAlive)
{
scraper[j] = null;
}
我想你想要if (!scraper[j].tred.IsAlive)
。否则,您最终将覆盖数组中的活动Scraper
引用。
更重要的是,尝试维护Scraper
对象的数组会导致很多你真正不需要的复杂功能。您已经拥有控制您可以拥有多少并发线程的信号量,因此Scraper
个对象的数组是不必要的噪声。
此外,您不希望一大堆ManualResetEvent
个对象等待。 WaitAll
不能等待超过63件商品,因此如果您的商品列表中的商品数量超过了该商品,WaitAll
将不会为您执行此操作。我在下面展示了确保完成所有工作的更好方法。
for (int i = 0; i < arrScrapeboxItems.Count; i++)
{
pool.WaitOne();
ScrapeBoxItems sbi = (ScrapeBoxItems)arrScrapeboxItems[i];
wb = new Gecko.GeckoWebBrowser();
PoolObjects po = new PoolObjects();
po.link = sbi.link;
// more initialization of po ...
// and then start the thread
Thread t = new Thread(ScrapeThreadProc);
t.Start(po);
}
// Here's how you wait for all of the threads to complete.
// You have your main thread (which is running here) call `WaitOne` on the semaphore 5 times:
for (int i = 0; i < 5; ++i)
{
pool.WaitOne();
}
private void ScrapeThreadProc(object o)
{
var po = (PoolObjects)o;
Scraper scraper = new Scraper();
// initialize your Scraper object
scraper.ThreadsCompleted += new Scraper.ThreadsHandler(frmMain_NextThreadItemsCompleted);
scraper.Scrape(po);
// scraping is done. Dispose of the scraper and the po.
// and then release the semaphore
pool.Release();
}
这应该会大大简化您的代码。
让主线程在信号量上等待5次的想法非常简单。如果主线程可以在不调用Release
的情况下获取信号量5次,那么您就知道没有其他任何工作正在运行。
还有其他方法可以做到这一点,但他们需要更多涉及的代码重组。您应该查看任务并行库,特别是Parallel.ForEach,它将为您处理线程。您可以将最大并发线程数设置为5,这样您就不会同时获得太多线程。
您也可以使用BlockingCollection
或其他共享队列的生产者/消费者设置来执行此操作。
在这两种情况下,您最终都会创建5个持久性线程,这些线程可以协同处理列表中的项目。这通常比为每个项目创建一个线程更有效。