我使用带有FileHelpers库的生产者/消费者模式,使用多个线程从一个文件(可能很大)中导入数据。每个线程都应该导入该文件的一大块,我想使用FileHelperAsyncEngine实例的LineNumber属性来读取该文件作为导入行的主键。 FileHelperAsyncEngine内部有一个IEnumerator IEnumerable.GetEnumerator(); 使用engine.ReadNext()方法迭代。这内部设置LineNumber属性(似乎不是线程安全的)。
消费者将让生产者与他们联系,向消费者提供DataTables,消费者将通过SqlBulkLoad类使用它们,这将使用IDataReader实现,该实现将遍历Consumer实例内部的DataTable集合。每个实例都有一个与之关联的SqlBulkCopy实例。
我有线程锁定问题。下面是我如何创建多个Producer线程。我开始后面的每个线程。将调用生产者实例上的生成方法,确定将处理哪个输入文件块。 似乎engine.LineNumber不是线程安全的,我没有在数据库中导入正确的LineNumber。似乎在engine.LineNumber的时候读取了一些名为engine.ReadNext()的其他线程并更改了engine.LineNumber属性。我不想锁定应该处理一大块输入文件的循环,因为我松散了并行性。如何重新组织代码来解决这个线程问题?
由于 Rad
for (int i = 0; i < numberOfProducerThreads; i++)
DataConsumer consumer = dataConsumers[i];
//create a new producer
DataProducer producer = new DataProducer();
//consumer has already being created
consumer.Subscribe(producer);
FileHelperAsyncEngine orderDetailEngine = new FileHelperAsyncEngine(recordType);
orderDetailEngine.Options.RecordCondition.Condition = RecordCondition.ExcludeIfBegins;
orderDetailEngine.Options.RecordCondition.Selector = STR_ORDR;
int skipLines = i * numberOfBufferTablesToProcess * DataBuffer.MaxBufferRowCount;
Thread newThread = new Thread(() =>
{
producer.Produce(consumer, inputFilePath, lineNumberFieldName, dict, orderDetailEngine, skipLines, numberOfBufferTablesToProcess);
consumer.SetEndOfData(producer);
});
producerThreads.Add(newThread); thread.Start();}
public void Produce(DataConsumer consumer, string inputFilePath, string lineNumberFieldName, Dictionary<string, object> dict, FileHelperAsyncEngine engine, int skipLines, int numberOfBufferTablesToProcess)
{
lock (this)
{
engine.Options.IgnoreFirstLines = skipLines;
engine.BeginReadFile(inputFilePath);
}
int rowCount = 1;
DataTable buffer = consumer.BufferDataTable;
while (engine.ReadNext() != null)
{
lock (this)
{
dict[lineNumberFieldName] = engine.LineNumber;
buffer.Rows.Add(ObjectFieldsDataRowMapper.MapObjectFieldsToDataRow(engine.LastRecord, dict, buffer));
if (rowCount % DataBuffer.MaxBufferRowCount == 0)
{
consumer.AddBufferDataTable(buffer);
buffer = consumer.BufferDataTable;
}
if (rowCount % (numberOfBufferTablesToProcess * DataBuffer.MaxBufferRowCount) == 0)
{
break;
}
rowCount++;
}
}
if (buffer.Rows.Count > 0)
{
consumer.AddBufferDataTable(buffer);
}
engine.Close();
}
答案 0 :(得分:2)
词典&LT;&GT;不是线程安全的。上面代码中的字典是正确锁定还是只用在你的锁中(这个)?
顺便说一句,我会避免使用lock(this)范例并使用通用对象来锁定代码。您可能遇到与特定资源无关的其他锁定问题。我在我的博客上详细说明了这个问题(Smart Resource Locking in C# .Net for Thread Safe Code)。 HTH
答案 1 :(得分:1)
你是对的LineNumber不是线程安全的:(
我只是调查代码,发现我们从内部阅读器读取了这一行,后来更新了LineNumber,因此根本没有线程安全。
问题在于,如果我们在内部添加一些sincronization代码,我们可以使事情变得非常慢,也许我们需要创建内部代码的线程安全版本以避免这种开销。
无论如何,我认为从性能角度来看,代码的较慢部分是文件操作,因此您无法使用多个线程进行读取。 也许您只需要一个线程将文件读取到工作队列,并且有多个线程可以读取它并使用每个记录,在这种情况下,您将获得所需的线程安全性
干杯
答案 2 :(得分:0)
我想我纠正了这个问题。这是字典&lt;&gt;需要锁定
锁定(字典) { dict [lineNumberFieldName] = engine.LineNumber; buffer.Rows.Add(ObjectFieldsDataRowMapper.MapObjectFieldsToDataRow(engine.LastRecord,dict,buffer)); } 感谢OmegaMan提供了一个很好的线索。