在C#中使用Parallel.ForEach循环以确保数据一致性的正确方法

时间:2019-01-22 11:02:14

标签: c# performance

我正在尝试通过遍历List<TestRequest>.来创建xml 为了获得更好的性能,我正在尝试Parallel.ForEach进行循环,因为列表中有成千上万条记录,但是我没有得到一致的数据 在xml中,有时在附加到字符串生成器的xml字符串中会出现截断,有时会出现数据不匹配的情况。 下面是代码

   public class Program
    {


        static void Main(string[] args)
        {
            List<TestRequest> ids = new List<TestRequest>();
            Random rnd = new Random();
            int id = rnd.Next(1, 12345);
            for (int i = 1; i < 1000; i++)
            {
                var data = new TestRequest();
                data.dataId = id;
                ids.Add(data);
            }

            var xmlData = GetIdsinXML(ids);

        }

        private static string GetIdsinXML(List<TestRequest> Ids)
        {
            var sb = new StringBuilder();
            sb.Append("<ROOT>");

            Parallel.ForEach(Ids, id =>
            {

                sb.Append("<Row");
                sb.Append(" ID='" + id.dataId + "'");
                sb.Append("></Row>");


            }
            );


            sb.Append("</ROOT>");
            return sb.ToString();
        }


    }

    public class TestRequest
    {
        public int dataId { get; set; }

    }

这是使用Parallel.ForEach的正确方法吗?

请帮助。 谢谢!

2 个答案:

答案 0 :(得分:2)

这是并行执行所需操作的最简单方法:

public class TestRequest
{
    public int dataId { get; set; }
    public string ToXml() => $"<row id=\"{dataId}\"/>";
}

class Program
{
    static void Main(string[] args)
    {
        const int n = 10000000;
        List<TestRequest> ids = new List<TestRequest>();
        Random rnd = new Random();
        for (int i = 1; i<n; i++)
        {
            var data = new TestRequest
            {
                dataId=rnd.Next(1, 12345)
            };
            ids.Add(data);
        }
        Stopwatch sw = Stopwatch.StartNew();
        var xml = GetIdsinXML(ids);
        sw.Stop();
        double time = sw.Elapsed.TotalSeconds;

        File.WriteAllText("result.xml", xml);
        Process.Start("result.xml");

        var output = $"Size={n} items, Time={time} sec, Speed={n/time/1000000} M/sec";

#if DEBUG
        Debug.WriteLine(output);
#else
        Console.WriteLine(output);
#endif

    }

    static string GetIdsinXML(List<TestRequest> requests)
    {
        // parallel query
        var list = requests.AsParallel().Select((item) => item.ToXml());
        // or sequential below:
        // var list = requests.Select((item) => item.ToXml());

        return $"<root>\r\n{string.Join("\r\n", list)}\r\n</root>";
    }
}

在糟糕的计算机上,没有.AsParallel()语句,按顺序执行,每秒可以执行约1,600,000次操作。有了并行语句,该速度将跃升至每秒2100,000次操作。

注意:我已将SpringBuilder替换为内置方法string.Join(string, IEnumerable list),Microsoft已对其进行了相当优化。有趣的一点是,“调试”构建与“发布”构建一样快甚至更快。走吧。

答案 1 :(得分:1)

使用PLINQ,您可以执行以下操作。

我不确定您是否真的需要这个。我只是为了回答而提出。

您的代码不起作用,因为StringBuilder不是线程安全的,并且您的操作也不是原子的,这意味着您的代码具有竞争条件。

sb.Append("<Row");
sb.Append(" ID='" + id.dataId + "'");
sb.Append("></Row>");

例如,一个线程可能执行第1行,而另一个线程紧随其后执行第3行。您将拥有<Row></Row>。这种比赛状态一直发生,最终结果是胡言乱语。

解决此问题的一种方法是在不同的线程上使用不同的StringBuilder,最后按顺序附加这些构建器的结果。

如果正在运行的线程执行的任务非常轻松并且很快完成,那么并行执行只会减慢程序速度。

return Ids.AsParallel()
   .Select((id, index) => (id, index))
   .GroupBy(x => x.index%Environment.ProcessorCount, x => x.id, (k, g) => g)
   .Select(g => 
   {
       var sb = new StringBuilder();
       foreach (var id in g)
       {
           sb.Append("<Row");
           sb.Append(" ID='" + id.dataId + "'");
           sb.Append("></Row>");
       }
       return sb.ToString();
   })
   .AsSequential()
   .Aggregate(new StringBuilder("<ROOT>"), (a, b) => a.Append(b))
   .Append("</ROOT>").ToString();

测量性能,看它是否确实有改善。如果不是不并行的话。