c#-使用'AsParallel()'/'Parallel.ForEach()'准则吗?

寻找有关利用AsParallel()Parallel.ForEach()加快速度的建议。

请参阅下面的方法(在本示例中为简化/严格化)。

它采用类似“ US,FR,APAC”的列表,其中“ APAC”是其他50个“ US,FR,JP,IT,GB”等国家的别名。 该方法应采用“ US,FR,APAC”,并将其转换为“ US”,“ FR”以及“ APAC”中所有国家/地区的列表。

private IEnumerable<string> Countries (string[] countriesAndAliases)
{
    var countries = new List<string>();

    foreach (var countryOrAlias in countriesAndAliases)
    {
        if (IsCountryNotAlias(countryOrAlias))
        {
            countries.Add(countryOrAlias);
        }
        else 
        {
            foreach (var aliasCountry in AliasCountryLists[countryOrAlias]) 
            {
                countries.Add(aliasCountry);
            }
        }
    }

    return countries.Distinct();
}

使并行化与将其更改为以下内容一样简单吗? 使用AsParallel()会比这有更多细微差别吗? 我应该使用Parallel.ForEach()而不是foreach吗? 并行处理foreach循环时,应使用什么经验法则?

private IEnumerable<string> Countries (string[] countriesAndAliases)
{
    var countries = new List<string>();

    foreach (var countryOrAlias in countriesAndAliases.AsParallel())
    {
        if (IsCountryNotAlias(countryOrAlias))
        {
            countries.Add(countryOrAlias);
        }
        else 
        {
            foreach (var aliasCountry in AliasCountryLists[countryOrAlias].AsParallel()) 
            {
                countries.Add(aliasCountry);
            }
        }
    }

    return countries.Distinct();
}
4个解决方案
69 votes

几点。

只写AliasCountryLists是没有用的。 Parallel.ForEach()成为并行执行后出现的Linq查询的一部分。 零件是空的,所以根本没有用。

通常,您应该使用Parallel.ForEach()替换AliasCountryLists。但是请注意不要使用线程安全代码! 你拥有了它。 您不能将其包装到foreach中,因为List<T>.Add本身不是线程安全的。

所以您应该这样做(对不起,我没有测试,但是可以编译):

        return countriesAndAliases
            .AsParallel()
            .SelectMany(s => 
                IsCountryNotAlias(s)
                    ? Enumerable.Repeat(s,1)
                    : AliasCountryLists[s]
                ).Distinct();

编辑:

您还必须确定两件事:

  1. AliasCountryLists必须是线程安全的。 如果它是纯函数,那就更好了。
  2. 同时,由于字典不是线程安全的,因此没有人会修改AliasCountryLists。 或使用ConcurrentDictionary确定。

有用的链接将帮助您:

并行编程的模式:通过.NET Framework 4理解和应用并行模式

.NET 4编码准则中的并行编程

什么时候应该使用Parallel.ForEach? 什么时候应该使用PLINQ?

PS:如您所见,新的并行功能并不像外观(和感觉)那么明显。

Andrey answered 2020-07-31T12:11:41Z
13 votes

使用AsParallel()时,需要确保您的身体是线程安全的。 不幸的是,以上代码无法正常工作。 ConcurrentBag<T>不是线程安全的,因此添加的AsParallel()将导致竞态。

但是,如果您将集合切换为使用System.Collections.Concurrent中的集合,例如ConcurrentBag<T>,则上述代码很可能会起作用。

Reed Copsey answered 2020-07-31T12:12:07Z
3 votes

我更喜欢为每个别名使用另一个数据结构(例如Set),然后使用Set union合并它们。

像这样

public string[] ExpandAliases(string[] countries){
    // Alias definitions
    var apac = new HashSet<string> { "US", "FR", ...};
    ... 

    var aliases = new HashMap<string, Set<string>> { {"APAC": apac}, ... };

    var expanded = new HashSet<string>
    foreach(var country in countries){
        if(aliases.Contains(country)
            expanded.Union(aliases[country]);
        else{
            expanded.Add(country);
    }

    return expanded.ToArray();
}

注意:代码应视为伪代码。

cjg answered 2020-07-31T12:12:36Z
0 votes

对我来说,这似乎是固有的串行操作。 您要做的就是遍历字符串列表并将其插入另一个列表。 并行化库将执行此操作,再加上一堆线程和同步-最终可能会变慢。

此外,如果您不想重复,则应该使用HashSet<string>

Steve M answered 2020-07-31T12:13:01Z
translate from https://stackoverflow.com:/questions/3780870/using-asparallel-parallel-foreach-guidelines