初识Parallel Extensions之TPL(二)

 

初识Parallel ExtensionsTPL(二)

LazyBee

前面我们谈了结构并行化的For循环(具体参见:初识Parallel Extensions之TPL),今天继续谈谈结构并行化的ForEach和Do。

ForEach

对于IEnumerable<T>的循环,并行扩展也提供了相应的并行实现,就是Parallel..ForEach.下面就让我们来看看,Parallel.ForEach的定义:

public static void ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body)

其中:source是要被处理的数据源,

body是针对每一个数据源中的元素要执行的操作。

例如:我们需要得到指定目录下所有扩展名为txt的文本文件中包含LazyBee的文件个数:

private static IEnumerable<string> GetFiles(string path,string extName)
{
    
if (path == nullthrow new ArgumentNullException("path");
    
if (File.Exists(path) && path.EndsWith(extName) )
    
{
        
yield return path;
    }

    
else if (Directory.Exists(path))
    
{
        
foreach (string file in Directory.GetFiles(path,"*."+extName)) 
yield return file;
        
foreach (string directory in Directory.GetDirectories(path))
        
{
            
foreach (string file in GetFiles(directory, "*."+extName)) 
yield return file;
        }

    }

}

private static bool FileContainsString(string text, string path)
{
    
using (StreamReader sr = new StreamReader(File.OpenRead(path)))
    
{
        
string line;
        
while ((line = sr.ReadLine()) != null)
        
{
          
if (line.IndexOf(text, 0, StringComparison.CurrentCultureIgnoreCase) >= 0
            
return true;
        }

    }

    
return false;
}

在有了上面的定义之后,我们就可以写我们的并行循环搜索了:

int count = 0;
Parallel.ForEach(GetFiles(path,
"txt"), file =>
{
    
if (FileContainsString(text, file)) Interlocked.Increment(ref count);
}
);

当然,Parallel.ForEach也提供了和Parallel.For相似的重载版本:

public static void ForEach<TSource>(IEnumerable<TSource> source,

Action<TSource, int, ParallelState> body)

 public static void ForEach<TSource, TLocal>(

IEnumerable<TSource> source,

                        Func<TLocal> threadLocalSelector,

Action<TSource, int, ParallelState<TLocal>> body,

                         Action<TLocal> threadLocalCleanup

)

其中,source是要被处理的数据源

        threadLocalSelector是产生线程本地状态的函数

body是每次循环(迭代)要进行的处理操作。这个操作都有三个输入参数,第一个

就是数据源中的元素,int类型的参数是该元素在数据源中的index.ParallelState类型的参数是用来在需要的情况下来终止循环的,ParallelState<TLocal>是用来保存线程本地状态以及需要时终止循环的。

       threadLocalCleanup是用于清理线程本地状态的操作

关于这些重载版本的使用和Parallel.For的使用类似,具体请参考初识Parallel Extensions之TPL的For循环部分的示例。

Do

Parallel.Do主要是用来并行化那些不存在相互依赖顺序的任务,这种情况在递归中是比较多见的,例如遍历树型结构时向左和向右没有顺序之分就可以使用它。具体我们还是来先看看其是如何定义的:

public static void Do(params Action[] actions)
public static void Do(Action[] actions, TaskManager manager,
 TaskCreationOptions options)

其中:actions是并行要执行的操作(任务)。这个Action代理类型是.Net3.5引入的,用于定义不带参数也没有返回值的操作。

manager是任务的调度管理器。你可以使用它配合TaskManagerPolicy来指定并行运

行的线程数。

        options是一个枚举类型,可以指定以下四种取值:

None指定使用缺省的行为。

SuppressExecutionContextFlow指定并不继承当前执行的上下文

RespectParentCancelation如果父任务取消,该任务也随之取消

SelfReplicating在处理器可用时将进行自我复制

下面我们来看一个例子,假设我们有一个带有整形数据的二叉树,我们现在需要遍历每一个节点,并且将每个节点的数值都加2,首先我们来定义我们的二叉树:

class Tree<T>
{
    
public T Data;
    
public Tree<T> Left, Right;
}

接下来,是填充二叉树数据的函数:

private static Random _rnd = new Random();
public static Tree<int> GenerateRandom(int depth)
{
    Tree
<int> root = new Tree<int> { Data = _rnd.Next() };
    
if (depth > 0)
    
{
        root.Left 
= GenerateRandom(depth - 1);
        root.Right 
= GenerateRandom(depth - 1);
    }

    
return root;
}

最后就是如何来实现我们的业务逻辑的代码了:

public static void MyParallel()
{
    Tree
<int> tree =GenerateRandom(5);
    ProcessTreeParallel(tree, (tmpTree, i) 
=> { tmpTree.Data += i; Console.WriteLine(tmpTree.Data); },2);
}


public static void ProcessTreeParallel(Tree<int> tree,Action<Tree<int>,int> action,int v1)
{
    
if (tree == nullreturn;
    Parallel.Do(() 
=> ProcessTreeParallel(tree.Left, action),
                 ()
=>ProcessTreeParallel(tree.Right, action),
                 ()
=>action(tree,v1));
}

Parallel.Do只有在语句中的所有任务都完成后才会继续往下走。
大家能看到有了
Lambda表达式,我们真是省去了很多的代码。

 

有关TaskManager以及TaskCreationOptions如何使用将在下一次任务并行性中来描述。

posted on 2008-03-28 18:01  懒蜜蜂  阅读(2061)  评论(6编辑  收藏  举报