多线程时如何传递不同的实例?

时间:2014-08-17 11:32:06

标签: c# multithreading

我正在建造一个刮刀。我的目标是启动X浏览器(其中X是线程数)并继续通过在X部分中拆分该列表来抓取每个URL的URL列表。

我决定使用3个线程(3个浏览器)和10个URL列表。

问题:如何在浏览器之间分隔每个任务:

  1. Browser1从0到3

  2. 中清除列表中的项目
  3. Browser2将列表中的项目从4到7

  4. Browser3从8到10

  5. 中清除列表中的项目

    所有浏览器都应该同时抓取传递的URL列表。

    我已经拥有此BlockingCollection

    BlockingCollection<Action> _taskQ = new BlockingCollection<Action>();
    
    public Multithreading(int workerCount)
    {
            // Create and start a separate Task for each consumer:
            for (int i = 0; i < workerCount; i++)
                Task.Factory.StartNew(Consume);
    }
    
    public void Dispose() { _taskQ.CompleteAdding(); }
    
    public void EnqueueTask(Action action) { _taskQ.Add(action); }
    
    void Consume()
    {
    // This sequence that we’re enumerating will block when no elements
    // are available and will end when CompleteAdding is called. 
    foreach (Action action in _taskQ.GetConsumingEnumerable())
                action();     // Perform task.
    }
    public int ItemsCount()
    {
            return _taskQ.Count;
    }
    

    可以像这样使用:

    Multithreading multithread = new Multithreading(3); //3 threads
    foreach(string url in urlList){
    
        multithread.EnqueueTask(new Action(() => 
        {
             startScraping(browser1); //or browser2 or browser3
        }));
    }
    

    我需要在抓取之前创建浏览器实例,因为我不想为每个线程启动一个新的浏览器。

4 个答案:

答案 0 :(得分:1)

考虑到Henk Holtermans可能需要最大速度的评论,即尽可能保持浏览器的忙碌,请使用:

private static void StartScraping(int id, IEnumerable<Uri> urls)
{
    // Construct browser here
    foreach (Uri url in urls)
    {
        // Use browser to process url here
        Console.WriteLine("Browser {0} is processing url {1}", id, url);
    }
}

在main中:

    int nrWorkers = 3;
    int nrUrls = 10;
    BlockingCollection<Uri> taskQ = new BlockingCollection<Uri>();
    foreach (int i in Enumerable.Range(0, nrWorkers))
    {
        Task.Run(() => StartScraping(i, taskQ.GetConsumingEnumerable()));
    }
    foreach (int i in Enumerable.Range(0, nrUrls))
    {
        taskQ.Add(new Uri(String.Format("http://Url{0}", i)));
    }
    taskQ.CompleteAdding();

答案 1 :(得分:1)

我认为通常的方法是拥有一个阻塞队列,一个提供者线程和一个任意的工作池。

提供程序线程负责将URL添加到队列。当没有人可以添加时,它会阻止。

工作线程实例化浏览器,然后从队列中检索单个URL,对其进行擦除,然后循环返回以获取更多信息。它在队列为空时阻塞。

你可以随心所欲地开始工作,他们只是在他们之间进行整理。

主线开始所有线程并退出边线。如果有UI,它会管理UI。

多线程可能很难调试。您可能希望至少部分工作使用“任务”。

答案 2 :(得分:0)

您可以为任务和工作人员提供一些Id。然后,您将BlockingCollection[]而不仅仅是BlockingCollection。每个消费者都会从数组中使用自己的BlockingCollection。我们的工作是找到合适的消费者并发布工作。

BlockingCollection<Action>[] _taskQ;
private int taskCounter = -1;
public Multithreading(int workerCount)
{
    _taskQ = new BlockingCollection<Action>[workerCount];

    for (int i = 0; i < workerCount; i++)
    {
        int workerId = i;//To avoid closure issue
        _taskQ[workerId] = new BlockingCollection<Action>();
        Task.Factory.StartNew(()=> Consume(workerId));
    }
}

public void EnqueueTask(Action action)
{
    int value = Interlocked.Increment(ref taskCounter);
    int index = value / 4;//Your own logic to find the index here    
    _taskQ[index].Add(action);
}

void Consume(int workerId)
{
    foreach (Action action in _taskQ[workerId].GetConsumingEnumerable())
       action();// Perform task.
}

答案 3 :(得分:0)

使用后台工作程序的简单解决方案可以限制线程数:

public class Scraper : IDisposable
{
    private readonly BlockingCollection<Action> tasks;
    private readonly IList<BackgroundWorker> workers;

    public Scraper(IList<Uri> urls, int numberOfThreads)
    {
        for (var i = 0; i < urls.Count; i++)
        {
            var url = urls[i];
            tasks.Add(() => Scrape(url));
        }

        for (var i = 0; i < numberOfThreads; i++)
        {
            var worker = new BackgroundWorker();
            worker.DoWork += (sender, args) =>
            {
                Action task;
                while (tasks.TryTake(out task))
                {
                    task();
                }
            };
            workers.Add(worker);
            worker.RunWorkerAsync();
        }
    }

    public void Scrape(Uri url)
    {
        Console.WriteLine("Scraping url {0}", url);
    }

    public void Dispose()
    {
        throw new NotImplementedException();
    }
}