C# C语言中foreach循环的性能优化#

C# C语言中foreach循环的性能优化#,c#,performance,foreach,parallel-processing,C#,Performance,Foreach,Parallel Processing,我有一个方法: IList<string> pages = new List<string>(); foreach (var node in nodes) { try { string temp = DoSomeComplicatedModificationOnNode(node); if (temp.ToLower().Contains(path)) { pages.Add(node

我有一个方法:

IList<string> pages = new List<string>();
foreach (var node in nodes)
{
    try
    {
        string temp = DoSomeComplicatedModificationOnNode(node);
        if (temp.ToLower().Contains(path))
        {
            pages.Add(node.Title);
        }
    }
    catch (Exception)
    {
        continue;
    }
}
IList pages=new List();
foreach(节点中的var节点)
{
尝试
{
string temp=dosomecomplementmodificationonnode(节点);
if(temp.ToLower().包含(路径))
{
pages.Add(节点.标题);
}
}
捕获(例外)
{
继续;
}
}
doSomeComplexedModificationOnNode()在某些情况下给出异常,这就是为什么使用try{}catch块的原因-我可以跳过给出异常的项。节点数包含数千个项目,一个项目具有多个属性。如何优化此循环?我正在考虑Parallel.Foreach,但是下面的代码给了我一个错误“缺少当前主体”:

IList pages=new List();
Parallel.ForEach(页面节点,节点=>
{
尝试
{
string temp=dosomecomplementmodificationonnode(节点);
if(temp.ToLower().包含(路径))
{
pages.Add(节点.标题);
}
}
捕获(例外)
{
}
});
列表在大多数情况下不是线程安全的。查看线程安全的集合,例如ConcurrentBag。

在C#中,泛型列表不是线程安全的,因此不能在并行循环中添加项

我建议使用另一个类,如ConcurrentStack或ConcurrentQueue

var pages = new ConcurrentBag<string>();
Parallel.ForEach(pageNodes, node =>
{
    try
    {
        string temp = DoSomeComplicatedModificationOnNode(node);
        if (temp.ToLower().Contains(path))
            pages.Add(node.Title);
    }
    catch (Exception)
    {
        throw;
    }
});

为此,我建议使用PLINQ。并行LINQ是LINQ的并行实现,具有相同的操作集。使用PLINQ编写的代码遵循函数式规则-没有任何更新,只是在并行模式下映射当前列表。它可以通过在不同的线程中运行映射程序,然后在单个“数据集”中收集结果,从而提高案例的性能。当然,它只能在CPU内核很少的情况下提高性能(但像往常一样,现在我们都只有很少的内核)

这里有一个例子

    private static void Main(string[] args)
    {
        var result =
            GenerateList()
                .AsParallel()
                .Select(MapToString)
                .Where(x => !String.IsNullOrWhiteSpace(x))
                .ToList();

        Console.ReadKey();
    }

    private const string Path = "1";
    private static string MapToString( int node)
    {
        //Console.WriteLine("Thread id: {0}", Thread.CurrentThread.ManagedThreadId);
        try
        {
            string temp = DoSomeComplicatedModificationOnNode(node);
            if (temp.ToLower().Contains(Path))
            {
                return temp;
            }
        }
        catch (Exception)
        {
            return null;
        }

        return null;
    }
    private static IEnumerable<int> GenerateList()
    {
        for (var i=0; i <= 10000; i++)
            yield return i;
    }

    private static string DoSomeComplicatedModificationOnNode(int node)
    {
        return node.ToString(CultureInfo.InvariantCulture);
    }
private static void Main(字符串[]args)
{
var结果=
生成论者()
.天冬酰胺()
.选择(映射字符串)
.Where(x=>!String.IsNullOrWhiteSpace(x))
.ToList();
Console.ReadKey();
}
private const string Path=“1”;
私有静态字符串映射字符串(int节点)
{
//WriteLine(“线程id:{0}”,Thread.CurrentThread.ManagedThreadId);
尝试
{
string temp=dosomecomplementmodificationonnode(节点);
if(temp.ToLower().包含(路径))
{
返回温度;
}
}
捕获(例外)
{
返回null;
}
返回null;
}
私有静态IEnumerable GenerateList()
{

对于(var i=0;i而言,真正的性能问题在于您正在捕获异常,只需尝试使用变量通知结果即可

对于长时间运行的方法,应该使用异步()方法

注意并行操作(在LINQ用户中),因为您的资源是有限的,并且在您最不希望的时候可能没有内存。而且您的代码必须是(列表不是线程安全的)

我敢打赌,您不会有比以下代码更好的性能:

var pages = nodes.Select(x => new { Status = DoSomeComplicatedModificationOnNode(x), Node = x })
    .Select(x => x?.Result)
    .Where(x => x.Status.IsCorrect && x.Status.ToLowerInvariant().Contains(path))
    .Select(x => x.Node.Title)
    .ToList();
或者使用Asynchronous+LINQ:

var pages = nodes.Select(async x =>
{
    return new { Status = await DoSomeComplicatedModificationOnNode(x), Node = x };
})
.Select(x => x?.Result)
.Where(x => x.Status.IsCorrect && x.Status.ToLowerInvariant().Contains(path))
.Select(x => x.Node.Title)
.ToList();

在调用
ToList
之前,将不会执行整个查询。

不,使用多个线程可能不会使循环更快。代码中有三个明显的错误,使循环运行更慢

  • 页面
    列表将被重新分配多次
  • 调用
    ToLower()
    会在每次运行时创建一个临时字符串
  • doSomedModificationOnNode
    可能会抛出
  • 请参阅下面的固定版本

    //为列表提供初始容量(最佳猜测),避免重新分配。
    var pages=新列表(nodes.Length);
    foreach(节点中的var节点)
    {
    字符串temp=doSomeComplementModificationOnNode(节点,输出变量错误);
    if(错误!=null)
    {
    继续;
    }
    //IndexOf()允许无分配的不区分大小写的字符串搜索。
    if(temp.IndexOf(path,StringComparison.CurrentCultureIgnoreCase)>=0)
    {
    pages.Add(节点.标题);
    }
    }
    
    你能复制你从Parralel对象得到的整个错误消息吗?捕获
    异常
    不是一个好主意,你应该捕获正确的异常。据我所知,DoSomeCompledModificationOnNode方法请求服务器中的代码?你的问题是什么?例如:如果DoSomeCompledModificationNNode没有使用很多perfdormance,循环悬置是最小的。抓起一个探查器,找出什么是对的或错的。作为旁注:您可能想使用
    ToLowerInvariant
    ,而不是
    ToLower
    。无知的注释。parallel.foreach的存在对列表的并行性没有任何影响。对于插入,列表不是线程安全的,这是一个问题。Parallel.Foreach有很多用途——在使用非线程保存容器的代码块上滥用它不是其中之一。这是一个有效的错误,但我不认为使用
    List
    会导致类似“缺少当前主体”的情况,如果性能很关键,避免同步并为每个线程使用单独的列表(最后合并)可以提供很好的加速,但我认为使用
    Parallel.ForEach
    ,我们不太容易做到这一点。
    var pages = nodes.Select(x => new { Status = DoSomeComplicatedModificationOnNode(x), Node = x })
        .Select(x => x?.Result)
        .Where(x => x.Status.IsCorrect && x.Status.ToLowerInvariant().Contains(path))
        .Select(x => x.Node.Title)
        .ToList();
    
    var pages = nodes.Select(async x =>
    {
        return new { Status = await DoSomeComplicatedModificationOnNode(x), Node = x };
    })
    .Select(x => x?.Result)
    .Where(x => x.Status.IsCorrect && x.Status.ToLowerInvariant().Contains(path))
    .Select(x => x.Node.Title)
    .ToList();