我怎样才能使这个 运行 更快?

How can i make this run faster?

我可以执行线程而不是任务来使 运行 更快吗? 我正在尝试将 114000 种产品放入数据库。正如我现在的代码一样,我每分钟将大约 100 个产品输入数据库。

我的任务(生产者)每个抓取一个 XML 包含产品数据的文件,将其打包到产品 class 中,然后为消费者排队。

我的消费者从队列中取出每个产品并将其放入数据库中,每次 1 个。我使用 Entity Framework 所以线程不安全。

public static void GetAllProductsFromIndexes_AndPutInDB(List<IndexModel> indexes, ProductContext context)
{
    BlockingCollection<IndexModel> inputQueue = CreateInputQueue(indexes);
    BlockingCollection<Product> productsQueue = new BlockingCollection<Product>(5000);

    var consumer = Task.Run(() =>
    {
        foreach (Product readyProduct in productsQueue.GetConsumingEnumerable())
        {
            InsertProductInDB(readyProduct, context);
        }
    });

    var producers = Enumerable.Range(0, 100)
        .Select(_ => Task.Run(() =>
        {
            foreach (IndexModel index in inputQueue.GetConsumingEnumerable())
            {
                Product product = new Product();
                byte[] unconvertedByteArray;
                string xml;
                string url = @"https://data.Icecat.biz/export/freexml.int/en/";

                unconvertedByteArray = DownloadIcecatFile(index.IndexNumber.ToString() + ".xml", url);
                xml = Encoding.UTF8.GetString(unconvertedByteArray);
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(xml);

                GetProductDetails(product, xmlDoc, index);

                XmlNodeList nodeList = (xmlDoc.SelectNodes("ICECAT-interface/Product/ProductFeature"));
                product.FeaturesLink = GetProductFeatures(product, nodeList);

                nodeList = (xmlDoc.SelectNodes("ICECAT-interface/Product/ProductGallery/ProductPicture"));
                product.Images = GetProductImages(nodeList);
                productsQueue.Add(product);
            }
        })).ToArray();

    Task.WaitAll(producers);
    productsQueue.CompleteAdding();
    consumer.Wait();
}

首先,阅读 speed rant 以确保这甚至值得研究。

Can I do threads instead of tasks to make this run faster?

极不可能。一段时间以来,多线程一直被用作实现多任务处理的廉价方式,但从技术上讲,它仅在任务 CPU 绑定 时才有用。您正在进行数据库操作。那一个将是网络绑定的。更有可能是数据库绑定(他们应用额外的瓶颈作为其可靠性和并发问题预防的一部分)。

Im trying to get 114000 products into the db.

那么您最好的选择是不要尝试在代码中这样做。每个值得占用内存的 DBMS 都有批量插入选项。在 C# 代码中这样做?这只会使它变得更慢且更不可靠。

充其量你给整个操作加上Network load发送数据到DB。在最坏的情况下,你让它变得更慢。这是 DB 最常见的错误之一,认为您可以用代码击败 DBMS 性能。它不会起作用。

必须做几件事。

插入每个产品实体后将其分离,否则它们将全部累积在更改跟踪器中。

不要在每个产品之后调用 SaveChanges。批量一百个左右。像这样:

var consumer = Task.Run(() =>
{
    var batch = new List<Product>();

    foreach (Product readyProduct in productsQueue.GetConsumingEnumerable())
    {
        batch.Add(readyProduct);
        if (batch.Count >= 100)
        {
            context.Products.AddRange(batch);
            context.SaveChanges();
            foreach (var p in batch)
            {
                context.Entry(p).State = EntityState.Detached;
            }
            batch.Clear();
        }

    }
    context.Products.AddRange(batch);
    context.SaveChanges();
    foreach (var p in batch)
    {
        context.Entry(p).State = EntityState.Detached;
    }

});

如果您使用的是 EF Core 并且您的提供商支持它(如 SQL 服务器),您甚至可以获得语句批处理。使用此处的基本最佳实践,您应该期望每秒有数百行。如果您需要更多,您可以切换到批量加载 API(例如 SQL 服务器的 SqlBulkCopy)。