【发布时间】:2018-03-13 14:12:01
【问题描述】:
我们在使用线程来加速进程导入数据时遇到问题,但我们无法解决,因此我们将其分解为多个部分。
一开始,我们有一个包含 100 万行的人工主键的数据源,我们假设获取数据,将其转换为 XML 并将其插入到另一个数据源中。想法是拥有更多线程,每个线程平均拆分行并完成工作,并行工作应该加快整个过程,对吗?但事实并非如此,因此我们只专注于每个线程拆分行,将其转换为 XML 并将其附加到内存中的某个数据表中以计算时间。
这是我们的工作:
private void btn_Start_Click(object sender, EventArgs e)
{
Thread MyThread = new Thread(Action_Start);
MyThread.Start();
}
void Action_Start()
{
string _Threads = text_Threads.Text; // obtaining amount of threads
string _Bucket = text_Bucket.Text; // obtaining amount of rows to
// process per thread
List<Task> MyTasks = new List<Task>();
for (int Index = 1; Index <= Convert.ToInt32(_Threads); Index++)
{
int MyIndex = Index;
MyTasks.Add(
Task.Factory.StartNew(
() => DoWork(MyIndex, Convert.ToInt32(_Bucket))));
}
Task.WaitAll(MyTasks.ToArray());
}
async void DoWork(int p_Index, int p_Bucket)
{
DataTable MyTable = new DataTable();
for (int Index = 1; Index <= 20; Index++)
{
DataColumn MyColumn = new DataColumn("FIELD_" +
Index.ToString("0000"), typeof(String));
MyTable.Columns.Add(MyColumn);
}
for (int Index = 1; Index <= p_Bucket; Index++)
{
DataRow MyRow = MyTable.NewRow();
for (int Index2 = 1; Index2 <= 20; Index2++)
{
string MyField = "FIELD_" + Index2.ToString("0000");
MyRow[MyField] = new String('0', 128);
}
MyTable.Rows.Add(MyRow);
}
Stopwatch MyTimer = new Stopwatch();
long Brojac = 1;
DataTableReader MyReader = MyTable.CreateDataReader();
MyTimer.Start();
while (await MyReader.ReadAsync())
{
string Result = "<Root>";
for (int Index = 1; Index <= 20; Index++)
{
string MyField = "FIELD_" + Index.ToString("0000");
XElement MyXml = new XElement("Property");
MyXml.SetAttributeValue("Value", MyReader[MyField]);
MyXml.SetAttributeValue("Field", MyField);
Result += MyXml.ToString();
}
Brojac++;
Result += "</Root>";
}
MyTimer.Stop();
MyReader.Close();
TimeSpan ts = MyTimer.Elapsed;
//TIPS_AND_TRICKS: How to format and display the TimeSpan value.
string elapsedTime = String.Format
("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);
string Buffer = "Processing time: " +
elapsedTime + "; Through-put: " +
Convert.ToInt32(Brojac / ts.TotalSeconds).ToString() +
" records per second; Total " +
p_Bucket.ToString("000000 ") + " records";
Poruka(Buffer);
}
所以这里最大的问题是当我们运行时:
1 thread for 50.000 rows; processing time is 00:00:05.49; On average 9099 records per second;
对
4 threads for 50.000 rows (12.5k per thread); average processing time per thread is 00:00:20.80; On average 2390 records per second;
我的问题是,为什么我们使用更多线程和任务后,平均处理时间和行数会减少?难道他们不应该并行处理并在几毫秒内吞噬这个数据集吗?
【问题讨论】:
-
可能是您的字符串连接消耗了“额外”时间。我的建议是使用 StringBuilder 对象并重复测量.. 但它有很多代码,这只是一个想法。
-
您能否在一个更简单、可验证的示例中重现此行为(请参阅minimal reproducible example)?它的方式取决于太多的实现细节并且很难分析。我相信存在某种锁定,阻止您的任务尽可能快地进行。你真的应该让自己成为一个分析器并进行根本原因分析,因为我们能告诉你的一切都是猜测。
-
那么时间去哪儿了?你测量了什么?您是否通过分析器运行代码?猜测代码在哪里运行缓慢是开发人员非常不擅长的事情。测量,测量,测量,然后当它没有意义时,回到我们这里。
-
一个线程生成任务,它异步执行同步任务,哦,天哪。首先,考虑将其写成
Parallel.ForEach或并行LINQ.AsParallel();这已经比手动切割要简单得多。其次,不要立即进行并行处理,而是看看你在做什么以及它是否已经不能在 single 线程上加速。对于初学者来说,DataTable是一个非常低效的类,因为它处理非类型化数据。其次,流数据通常比一次对所有内容进行大量转换更有效。 -
Result += MyXml.ToString();在许多附加的情况下会让你付出代价,但这可能不是你最大的问题。
标签: c# multithreading task