C#中foreach循环的性能优化

时间:2014-04-25 09:29:52

标签: 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.Title);
        }
    }
    catch (Exception)
    {
        continue;
    }
}

DoSomeComplicatedModificationOnNode()在某些情况下会出现异常,这就是使用try {} catch块的原因 - 我可以跳过提供异常的项目。节点数包含数千个项目,项目具有多个属性。如何优化此循环?我在考虑Parallel.Foreach,但是下面的代码给出了一个错误“Missing current principal”:

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

5 个答案:

答案 0 :(得分:9)

在C#中,通用列表不是线程安全的,因此您无法在并行循环中添加项目。

我建议使用另一个类,如ConcurrentBag,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;
    }
});

请记住,并行任务是无序的,如果您需要订单,则必须在并行中使用索引。列表只是thead-save for reading。

System.Threading.Tasks.Parallel.For(0, pageNodes.Count, index =>
{
    string node = pageNodes[index];

    try
    {
        string temp = DoSomeComplicatedModificationOnNode(node);
        if (temp.ToLower().Contains(path))
            pages.Add(MyPage(index, node.Title));
    }
    catch (Exception)
    {
        throw;
    }
});

答案 1 :(得分:3)

我建议将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);
    }

答案 2 :(得分:2)

列表与LT; T&GT;在大多数情况下,它不是线程安全的。看看线程安全的集合,例如ConcurrentBag&LT; T&GT;

答案 3 :(得分:0)

真正的性能问题是您正在捕获异常,只是尝试使用变量通知结果。

对于长时间运行的方法,应使用异步(asyc/await)方法。

当心并行操作(在LINQ用户AsParallel()中使用),因为您的资源有限,并且在您最不期望的时候可能没有内存。并且您的代码必须为thread-safe(列表不是线程安全的)。

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

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();

或使用异步+ 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之前,不会执行整个查询。

答案 4 :(得分:0)

否,使用多个线程可能不会使循环更快。您的代码中存在三个明显的错误,它们使运行速度变慢。

  1. pages列表将被重新分配多次。
  2. ToLower()的调用在每次运行时都会创建一个临时字符串。
  3. DoSomeComplicatedModificationOnNode可能会抛出。

请参阅下面的固定版本。

// Give the list an initial capacity (best guess), avoiding re-allocations.
var pages = new List<string>(nodes.Length);
foreach (var node in nodes)
{
    string temp = DoSomeComplicatedModificationOnNode(node, out var error);
    if (error != null)
    {
        continue;
    }

    // IndexOf() allows for allocation-free case insensitive string search.
    if (temp.IndexOf(path, StringComparison.CurrentCultureIgnoreCase) >= 0)
    {
        pages.Add(node.Title);
    }
}