Как я могу сделать это быстрее? - PullRequest
0 голосов
/ 07 февраля 2020

Могу ли я выполнять потоки вместо задач, чтобы они работали быстрее? Я пытаюсь получить 114000 продуктов в базу данных. Так как мой код прямо сейчас, я получаю около 100 продуктов в базу данных в минуту.

Мои задачи (производители) каждый очищают файл XML, который содержит данные о продукте, упаковывает его в класс Product, затем помещает в очередь это для потребителя.

мой потребитель берет каждый продукт из очереди и помещает его в базу данных по 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();
}

Ответы [ 2 ]

1 голос
/ 07 февраля 2020

Сначала прочитайте speed rant , чтобы убедиться, что это даже стоит исследовать.

Могу ли я выполнять потоки вместо задач, чтобы ускорить этот процесс?

Крайне маловероятно. Многопоточность некоторое время использовалась как дешевый способ реализации многозадачности, но технически она полезна только в том случае, если задача CPU CPU . Вы делаете операцию с БД. Это будет связано с сетью. Скорее всего, это связано с базой данных (они применяют дополнительные узкие места в качестве части предотвращения проблем надежности и параллелизма).

Я пытаюсь получить 114 000 продуктов в базу данных.

Тогда ваш Лучшая ставка - , не пытаясь сделать это в коде . Каждая СУБД, которая стоит своего объема памяти, имеет опции массовой вставки. Делать это в C# коде? Это только сделает его медленнее и менее надежным.

В лучшем случае вы добавляете сетевую нагрузку для отправки данных в БД для всей операции. В худшем случае вы делаете это еще медленнее, чем это. Это одна из самых распространенных ошибок с БД, когда вы думаете, что с помощью кода вы можете повысить производительность СУБД. Это не сработает.

0 голосов
/ 07 февраля 2020

Пара вещей, которые вы должны сделать.

Отсоедините каждую сущность Продукта после того, как вы ее выполнили, иначе все они будут накапливаться в трекере изменений.

Не ' Вызвать 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 Сервер), вы даже получите пакетную инструкцию. Вы должны ожидать несколько сотен строк в секунду, используя здесь основные приемы c. Если вам нужно больше, вы можете переключиться на API массовой загрузки (например, SqlBulkCopy для SQL Server).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...