[英]Tasks in Recursion
我具有递归遍历二叉树的功能。 由于操作是计算密集型的,因此我想到使用以下任务在递归函数中生成多个线程:
static void Traverse<T>(Tree<T> node, Action<T> action)
{
if (node == null) return;
var t1 = Task.Factory.StartNew(() => action(node.Data));
var t2 = Task.Factory.StartNew(() => Traverse(node.Left, action));
var t3 = Task.Factory.StartNew(() => Traverse(node.Right, action));
Task.WaitAll(t1, t2, t3);
}
现在看来确实可行。 但是我想知道在以递归方式使用任务时是否需要注意任何事项。 例如,如果树的深度很长,它是否会以某种方式无法创建较低级别的任务并等待其他任务完成(因为它们正在等待较低级别的任务完成,因此永远无法完成)?
如果生成的树很大,那么许多任务可能会导致问题完全耗尽整个线程池,从而导致其他地方的性能问题,这是因为节点及其父节点之间没有依赖关系,因此所有节点都将尝试同时运行。 我要做的是让您的Tree<T>
类实现IEnumerable<T>
,该方法将返回其自己的Data
属性以及所有其子级的Data
属性,然后使用Parallel.ForEach
static void Traverse<T>(Tree<T> node, Action<T> action)
{
Parallel.ForEach(node, action);
}
//Elsewhere
class Tree<T> : IEnumerable<T>
{
Tree<T> Left { get; set; }
Tree<T> Right { get; set; }
T Data { get; set; }
public IEnumerator<T> GetEnumerator()
{
yield return this.Data;
if (Left != null)
{
foreach (var left in Left)
{
yield return left.Data;
}
}
if (Right != null)
{
foreach (var right in Right)
{
yield return right.Data;
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
您需要关注的唯一“ Gotcha”是,如果树中存在任何闭环,其中子代可能是更高级别节点的父代,这将导致无限递归。
编辑 :这是一个新版本,它不对GetEnumerator
使用递归,而是使用Stack<Tree<T>>
对象来保存状态,因此,如果您有极高的树,则不能有StackOverflowException
。 同样,如果您从注释行中删除注释,它将停止以前版本的“无限递归”问题。 但是,如果您知道没有任何循环结构,则没有必要,因此我将其注释掉。
class Tree<T> : IEnumerable<T>
{
Tree<T> Left { get; set; }
Tree<T> Right { get; set; }
T Data { get; set; }
public IEnumerator<T> GetEnumerator()
{
Stack<Tree<T>> items = new Stack<Tree<T>>();
//HashSet<Tree<T>> recursiveCheck = new HashSet<Tree<T>>();
items.Push(this);
//recursiveCheck.Add(this);
while (items.Count > 0)
{
var current = items.Pop();
yield return current.Data;
if (current.Left != null)
//if(recursiveCheck.Add(current.Left))
items.Push(current.Left);
if (current.Right != null)
//if (recursiveCheck.Add(current.Right))
items.Push(current.Right);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
就像您说的那样,递归地生成线程似乎不是一个好主意,如果树足够长,您将得到很多线程,这会变慢,因为会有很多开销,或者最终达到程序中并行线程的极限。 因此,我建议您改用ThreadPool来管理线程。
您可能有一个线程来导航树,另外两个线程来完成繁重的工作。 您还应该注意,除非您有一些阻止操作(例如I / O读/写或正在进行一些网络连接),否则使用线程将不是很好。 如果您不这样做,则最好只使用一个线程来进行繁重的工作,而使用一个线程来遍历树。
我不认为它会在任何时候停止工作,但是使用多线程会增加CPU使用率,因为计算机一次执行更多操作,因此不使用多线程而只是使用多线程可能更安全但更慢。以下:
static void Traverse<T>(Tree<T> node, Action<T> action)
{
if (node == null) return;
action(node);
Traverse(node.Left, action);
Traverse(node.Right, action);
}
不过,这会比较慢,因此,如果您担心它的运行速度,您将希望使用原始版本。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.