C# 用于并行添加或更新的ConcurrentDictionary和ConcurrentBag
使用ConcurrentDictionary和ConcurrentBag添加或更新值是否正确 基本上是这样做的,C# 用于并行添加或更新的ConcurrentDictionary和ConcurrentBag,c#,multithreading,parallel.foreach,concurrentdictionary,C#,Multithreading,Parallel.foreach,Concurrentdictionary,使用ConcurrentDictionary和ConcurrentBag添加或更新值是否正确 基本上是这样做的, 拥有数百万条记录的文件,并试图处理和提取到对象 条目类似于键值对,Key=WBAN,Value为object var cd=new concurrentdirectionary(); 整数计数=0; foreach(文件.ReadLines(path).AsParallel()中的var行,带degreeofparallelism(5)) { var sInfo=line.Spli
var cd=new concurrentdirectionary();
整数计数=0;
foreach(文件.ReadLines(path).AsParallel()中的var行,带degreeofparallelism(5))
{
var sInfo=line.Split(新字符[]{',});
cd.AddOrUpdate(sInfo[0],new ConcurrentBag(){new Data())
{
WBAN=sInfo[0],
日期=字符串。IsNullOrEmpty(sInfo[1])?“”:sInfo[1],
time=string.IsNullOrEmpty(sInfo[2])?“”:sInfo[2]
}
}
,
(oldKey,oldValue)=>
{
添加(新数据()
{
WBAN=sInfo[0],
日期=字符串。IsNullOrEmpty(sInfo[1])?“”:sInfo[1],
time=string.IsNullOrEmpty(sInfo[2])?“”:sInfo[2]
});
返回旧值;
}
);
}
- 您的程序是IO绑定的,而不是CPU绑定的,因此并行处理没有任何好处。
- 它是IO绑定的,因为如果不先从文件中读取一行数据,程序就无法处理该行数据,一般来说,计算机从存储器中读取数据的速度总是比处理数据的速度慢得多
- 由于您的程序在读取的每一行上只执行琐碎的字符串操作,我可以有99.9%的把握说,将
元素添加到数据
所需的时间只是您的计算机从文本文件中读取一行所需时间的一小部分字典
- 此外,避免对此类程序使用
,因为这样会首先将整个文件读入内存。File.ReadLines
- 如果你看看我的解决方案,你会发现它使用
一行一行地读取每一行,这意味着它不需要等到它首先将所有内容读入内存StreamReader
- 如果你看看我的解决方案,你会发现它使用
private static readonly Char[] _sep = new Char[] { ',' }; // Declared here to ensure only a single array allocation.
public static async Task< Dictionary<String,List<Data>> > ReadFileAsync( FileInfo file )
{
const Int32 ONE_MEGABYTE = 1 * 1024 * 1024; // Use 1MB+ sized buffers for async IO. Not smaller buffers like 1024 or 4096 as those are for synchronous IO.
Dictionary<String,List<Data>> dict = new Dictionary<String,List<Data>>( capacity: 1024 );
using( FileStream fs = new FileStream( path, FileAccess.Read, FileMode.Open, FileShare.Read, ONE_MEGABYTE, FileOptions.Asynchronous | FileOptions.SequentialScan ) )
using( StreamReader rdr = new StreamReader( fs ) )
{
String line;
while( ( line = await rdr.ReadLineAsync().ConfigureAwait(false) ) != null )
{
String[] values = line.Split( sep );
if( values.Length < 3 ) continue;
Data d = new Data()
{
WBAN = values[0],
Date = values[1],
time = values[2]
};
if( !dict.TryGetValue( d.WBAN, out List<Data> list ) )
{
dict[ d.WBAN ] = list = new List<Data>();
}
list.Add( d );
}
}
}
private static readonly Char[]\u sep=new Char[]{',};//在此处声明以确保仅分配单个数组。
公共静态异步任务ReadFileAsync(FileInfo文件)
{
const Int32 ONE_MEGABYTE=1*1024*1024;//为异步IO使用1MB+大小的缓冲区。而不是像1024或4096这样的较小缓冲区,因为它们用于同步IO。
Dictionary dict=新字典(容量:1024);
使用(FileStream fs=newfilestream(路径,FileAccess.Read,FileMode.Open,FileShare.Read,1兆字节,FileOptions.Asynchronous | FileOptions.SequentialScan))
使用(StreamReader rdr=新StreamReader(fs))
{
弦线;
while((line=await rdr.ReadLineAsync().ConfigureAwait(false))!=null)
{
字符串[]值=行分割(sep);
如果(值长度<3)继续;
数据d=新数据()
{
WBAN=值[0],
日期=值[1],
时间=值[2]
};
如果(!dict.TryGetValue(d.WBAN,输出列表))
{
dict[d.WBAN]=list=newlist();
}
列表.添加(d);
}
}
}
更新:假设说。。。 假设地说,由于文件IO(特别是异步
FileStream
IO)使用大的缓冲区(在本例中为1兆字节大小的缓冲区),程序可以将每个缓冲区(在读取时,按顺序)传递到并行处理器中
然而,问题是缓冲区内的数据不能简单地分配给各个线程:在这种情况下,因为行的长度不是固定的,所以单个线程仍然需要读取整个缓冲区以找出换行符的位置(从技术上讲,可以进行某种程度的并行化,这将增加大量的复杂性(因为您还需要处理跨越缓冲区边界的行,或者只包含一行的缓冲区,等等)
在这种小规模下,使用线程池和并发收集类型的开销将消除并行处理的速度,因为程序在很大程度上仍然是IO受限的
现在,如果您有一个大小为千兆字节的文件,并且数据记录的大小约为1KB,那么我将详细演示如何做到这一点,因为在这种规模下,您可能会看到适度的性能提升。您的想法基本上是正确的,但实现中存在一个缺陷使用foreach
语句的lelQuery
不会导致循环内的代码并行运行。在此阶段,并行化阶段已经完成。在您的代码中,实际上没有并行工作,因为.aspallel()之后没有附加运算符。WithDegreeOfParallelism(5)
。要并行执行循环,必须用运算符替换foreach
,如下所示:
File.ReadLines(path)
.AsParallel()
.WithDegreeOfParallelism(5)
.ForAll(line => { /* Process each line in parallel */ });
了解什么是并行的很重要。每行的处理都是并行的,而从文件系统加载每行则不是。加载是序列化的。并行LINQ引擎(其中一个是当前线程)使用的工作线程在访问源IEnumerable
(本例中为文件.ReadLines(路径)
)
使用嵌套的ConcurrentDictionary
结构来存储处理后的行不是很有效。与手动处理并发集合等操作相比,您可以放心在数据分组方面做得更好。
通过使用操作符,您可以得到一个ILookup
,我
File.ReadLines(path)
.AsParallel()
.WithDegreeOfParallelism(5)
.ForAll(line => { /* Process each line in parallel */ });
var separators = new char[] { ',' };
var lookup = File.ReadLines(path)
.AsParallel()
.WithDegreeOfParallelism(5)
.Select(line => line.Split(separators))
.ToLookup(sInfo => sInfo[0], sInfo => new Data()
{
WBAN = sInfo[0],
Date = string.IsNullOrEmpty(sInfo[1]) ? "" : sInfo[1],
time = string.IsNullOrEmpty(sInfo[2]) ? "" : sInfo[2]
});