信号量似乎不适用于多个线程

时间:2014-09-17 22:34:28

标签: c# multithreading .net-3.5

我想同时运行多个线程(例如,最多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();
    }
}

1 个答案:

答案 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个持久性线程,这些线程可以协同处理列表中的项目。这通常比为每个项目创建一个线程更有效。