我怎样才能使这个 运行 更快?
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)。
我可以执行线程而不是任务来使 运行 更快吗? 我正在尝试将 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)。