【问题标题】:Multiple tasks using Parallel Foreach and WhenAny. Is that possible?使用 Parallel Foreach 和 WhenAny 的多个任务。那可能吗?
【发布时间】:2017-02-04 18:10:38
【问题描述】:

我想同时抓取几个网站,但只是将信息一个一个添加到数据库中。同时我的代码看起来与此类似:

        List<SiteMetadata> sitesList = GetSites();
        var tasks = new List<Task<SiteMetadata>>();

        foreach (var item in sitesList)
            tasks.Add(item.LoadMetaDataAsync());

        int totalSites = sitesList.Count;
        int finishedSites = 0;

        int errors = 0;
        while (totalSites != finishedSites)
        {
            var tempSite = await Task.WhenAny(tasks.ToArray());
            //WRITE HERE TO DB!!!!!!!!!!!!!!!!!
            tasks.Remove(tempSite);

            var tempLog = apiHandler.WriteToDatabase(tempSite.Result);

            if (tempLog.Type == LogType.Error)
            {
                errors++;
                LogsHandler.AddToLog(tempLog);
            }

            finishedSites++;
        }

我要的是提高这里的效率并替换掉:

    var tasks = new List<Task<SiteMetadata>>();
    foreach (var item in sitesList)
        tasks.Add(item.LoadMetaDataAsync());

到这样的事情:

    var runAll = Task.Factory.StartNew(() => Parallel.ForEach(sitesList, item => item.LoadMetaDataAsync()));

但问题是我不知道如何将完成的第一个任务逐个获取到数据库。无论如何,使用 Parallel 或类似的东西,甚至比我现在正在做的更有效的东西来做到这一点? 提前致谢。

【问题讨论】:

  • 您可以尝试实现一个在LoadMetaDataAsync 末尾触发的事件。此事件将在任务完成时通知您。那么你只需要用你的数据填充你的数据库到事件处理函数中
  • 在异步操作中抛出更多线程无济于事。您不受核心数量的限制,无论如何您都受到网络的限制。异步方法(正确编写时)立即产生控制。
  • 我在您发布的代码中没有看到任何暗示您的提案会“提高效率”,甚至与您所拥有的一样好现在。如果您认为可以,请提供一个良好的 minimal reproducible example,它可以可靠地重现您现在遇到的效率问题,解释该问题是什么,以及为什么您认为可以采取一些措施来改进它。

标签: c# .net task-parallel-library task c#-5.0


【解决方案1】:

我认为您正面临“多提供者 - 一个消费者”的问题。我建议你使用Thread-Safe Collections

在下面的控制台示例中,我使用ConcurrentBag 来存储任务结果,然后在主线程中,我使用while 循环来获取结果并将其打印出来(您可以在自己的工作线程中执行此操作)。注意整个程序中没有任何锁:

    private static readonly Random Random = new Random(DateTime.Now.Millisecond);
    private static readonly ConcurrentBag<int> Bag = new ConcurrentBag<int>();

    private static void Main(string[] args)
    {
        for (int i = 0; i < 10; i++)
        {
            Task.Run(async () => await SampleTask());
        }

        while (true)
        {
            if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Escape) break;
            int item;
            if (Bag.TryTake(out item))
                Console.WriteLine(item);
        }
    }

    private static async Task SampleTask()
    {
        await Task.Delay(Random.Next(1000));
        Bag.Add(Random.Next(10));
    }

【讨论】:

  • 好的。我认为这已经更好了,但无论如何这里可以使用 Parallel.Foreach 吗?
  • @juanora 如果你的循环体是“异步”的,就没有必要使用Parallel.Foreach
【解决方案2】:

我想同时抓取几个网站,但只是将信息一个一个添加到数据库中。

您的代码已经这样做了。

我要的是提高这里的效率并替换

这不会提高效率;它会减少它。 Parallel.ForEach 是并行操作,其中“并行”表示“使用多个线程并发”。启动多个任务,然后将它们与Task.WhenAll 组合是您如何在不使用多个线程的情况下进行并发使用不必要的线程更有效。

但是,您正在做的事情似乎可以从 TPL 数据流中受益,它允许您定义一个“管道”来发送数据。它不会增加你的“效率”,但它可能会澄清代码。

【讨论】:

  • 首先,感谢您的回答。为什么只使用一个线程比使用多个线程更有效?
  • 每个线程都是一个内核对象,并为其堆栈保留了相当多的内存。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
  • 2022-01-04
  • 2013-12-11
  • 2017-02-13
  • 1970-01-01
  • 2019-07-25
  • 1970-01-01
相关资源
最近更新 更多