Thread safety for DataTable
我已经阅读了ADO.NET DataTable / DataRow线程安全性的此答案,并且无法理解某些内容。
特别是我听不懂[2]文章。 我需要使用哪种包装纸?
谁能举一个例子?
我也无法理解作者所说的级联锁和全锁。 也请举例。
-
无需同时处理
DataTable (涉及突变时),或者: -
删除
DataTable ,而不是使用直接支持您需要的数据结构(例如并发集合),或者更简单并且可以轻松同步的数据结构(互斥或读取器/写入器)
基本上:改变问题。
来自评论:
The code looks like:
1
2
3
4
5
6
7
8
9
10
11 Parallel.ForEach(strings, str=>
{
DataRow row;
lock(table){
row= table.NewRow();
}
MyParser.Parse(str, out row);
lock(table){
table.Rows.Add(row)
}
});
我只希望
1 2 3 4 5 6 | Parallel.ForEach(strings, str=> { object[] values = MyParser.Parse(str); lock(table) { table.Rows.Add(values); } }); |
上面的重要更改是
然而!我仍然认为您采用了错误的方法:要使并行性有意义,它必须是非平凡的数据。如果您有非平凡的数据,那么您确实不需要将其全部缓存在内存中。我强烈建议您执行以下操作,在单个线程上可以正常工作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using(var bcp = new SqlBulkCopy()) using(var reader = ObjectReader.Create(ParseFile(path))) { bcp.DestinationTable ="MyLog"; bcp.WriteToServer(reader); } ... static IEnumerable<LogRow> ParseFile(string path) { using(var reader = File.OpenText(path)) { string line; while((line = reader.ReadLine()) != null) { yield return new LogRow { // TODO: populate the row from line here }; } } } ... public sealed class LogRow { /* define your schema here */ } |
优点:
-
无缓冲-这是完全流式操作(
yield return 不会将内容放入列表或类似内容) - 因此,这些行可以立即开始流式传输,而无需等待整个文件先被预处理
- 没有内存饱和问题
- 没有线程并发症/开销
- 您可以保留原始顺序(通常不重要,但很好)
- 您仅受读取原始文件速度的限制,通常在单个线程上读取该文件要比从多个线程读取文件快(单个IO设备上的争用只是开销)
-
避免了
DataTable 的所有开销,这在这里是过大的-因为它是如此灵活,所以开销很大 - (从日志文件中)读取和(向数据库中)写入现在是并发的,而不是顺序的
我在自己的工作中做了很多类似^^^的事情,从经验来看,它通常至少比首先在内存中填充
最后-这是一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | using System; using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; /// <summary> /// Acts as a container for concurrent read/write flushing (for example, parsing a /// file while concurrently uploading the contents); supports any number of concurrent /// writers and readers, but note that each item will only be returned once (and once /// fetched, is discarded). It is necessary to Close() the bucket after adding the last /// of the data, otherwise any iterators will never finish /// </summary> class ThreadSafeBucket< T > : IEnumerable< T > { private readonly Queue< T > queue = new Queue< T >(); public void Add(T value) { lock (queue) { if (closed) // no more data once closed throw new InvalidOperationException("The bucket has been marked as closed"); queue.Enqueue(value); if (queue.Count == 1) { // someone may be waiting for data Monitor.PulseAll(queue); } } } public void Close() { lock (queue) { closed = true; Monitor.PulseAll(queue); } } private bool closed; public IEnumerator< T > GetEnumerator() { while (true) { T value; lock (queue) { if (queue.Count == 0) { // no data; should we expect any? if (closed) yield break; // nothing more ever coming // else wait to be woken, and redo from start Monitor.Wait(queue); continue; } value = queue.Dequeue(); } // yield it **outside** of the lock yield return value; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } static class Program { static void Main() { var bucket = new ThreadSafeBucket<int>(); int expectedTotal = 0; ThreadPool.QueueUserWorkItem(delegate { int count = 0, sum = 0; foreach(var item in bucket) { count++; sum += item; if ((count % 100) == 0) Console.WriteLine("After {0}: {1}", count, sum); } Console.WriteLine("Total over {0}: {1}", count, sum); }); Parallel.For(0, 5000, new ParallelOptions { MaxDegreeOfParallelism = 3 }, i => { bucket.Add(i); Interlocked.Add(ref expectedTotal, i); } ); Console.WriteLine("all data added; closing bucket"); bucket.Close(); Thread.Sleep(100); Console.WriteLine("expecting total: {0}", Interlocked.CompareExchange(ref expectedTotal, 0, 0)); Console.ReadLine(); } } |
面对相同的问题,我决定实现嵌套的ConcurrentDictionaries
它是通用的,但可以更改为使用定义的类型。
包含转换为DataTable的示例方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | /// <summary> /// A thread safe data table /// </summary> /// <typeparam name="TX">The X axis type</typeparam> /// <typeparam name="TY">The Y axis type</typeparam> /// <typeparam name="TZ">The value type</typeparam> public class HeatMap<TX,TY,TZ> { public ConcurrentDictionary<TX, ConcurrentDictionary<TY, TZ>> Table { get; set; } = new ConcurrentDictionary<TX, ConcurrentDictionary<TY, TZ>>(); public void SetValue(TX x, TY y, TZ val) { var row = Table.GetOrAdd(x, u => new ConcurrentDictionary<TY, TZ>()); row.AddOrUpdate(y, v => val, (ty, v) => val); } public TZ GetValue(TX x, TY y) { var row = Table.GetOrAdd(x, u => new ConcurrentDictionary<TY, TZ>()); if (!row.TryGetValue(y, out TZ val)) return default; return val; } public DataTable GetDataTable() { var dataTable = new DataTable(); dataTable.Columns.Add(""); var columnList = new List<string>(); foreach (var row in Table) { foreach (var valueKey in row.Value.Keys) { var columnName = valueKey.ToString(); if (!columnList.Contains(columnName)) columnList.Add(columnName); } } foreach (var s in columnList) dataTable.Columns.Add(s); foreach (var row in Table) { var dataRow = dataTable.NewRow(); dataRow[0] = row.Key.ToString(); foreach (var column in row.Value) { dataRow[column.Key.ToString()] = column.Value; } dataTable.Rows.Add(dataRow); } return dataTable; } } |
介绍
如果并发或并行性是DataTable对象程序的要求,则可以做到这一点。我们来看两个示例(基本上,我们将在所有示例中看到常见的AsEnumerable()方法的使用):
在DataTable上进行1-并行迭代:
.NET提供了本机资源以在DataTable上并行进行迭代,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | DataTable dt = new DataTable(); dt.Columns.Add("ID"); dt.Columns.Add("NAME"); dt.Rows.Add(1,"One"); dt.Rows.Add(2,"Two"); dt.Rows.Add(3,"Three"); dt.PrimaryKey = new DataColumn[] { dt1.Columns["ID"] }; Parallel.ForEach(dt.AsEnumerable(), row => { int rowId = int.Parse(row["ID"]); string rowName = row["NAME"].ToString(); //TO DO the routine that useful for each DataRow object... }); |
2-将多个项目添加到数据表:
我认为这是不平凡的方法,因为DataTable的核心不是线程安全的集合/矩阵。然后,您需要ConcurrentBag的支持以保证不破坏您代码上的Exception。
在" ConcurrentBag-是否添加多个项目?"中,考虑到编程需要在DataTables上使用并发性,我编写了一个详细的示例,其中将DataTable对象中的项目添加到ConcurrentBag派生类中。然后,可以在使用并行资源的ConcurrentBag上添加了程序业务规则之后,将ConcurrentBag集合转换为DataTable。