使用匿名方法和简单线程的局部递归






4.68/5 (13投票s)
匿名方法有助于保持递归处理的 "环境" 被封装
引言
处理多个变量时,递归函数可能会变得稍微复杂。这些变量要么必须成为类成员(违反封装规则),要么必须作为参数通过所有递归(自)调用传递。
局部递归可以在周围方法的局部变量上工作。
我们开始吧
private void toolStripButton1_Click(object sender, EventArgs e) {
Action<string> Recurse = null;
Recurse = (sDir) => {
Console.WriteLine(sDir);
foreach (var S in System.IO.Directory.GetDirectories(sDir)) {
Recurse(S);
}
};
Recurse(@"..\..");
}
这是最基本的,只是为了展示原理:创建一个匿名 Action,然后调用它。
唯一不寻常的特性是:你不能在一行中声明和初始化。
Action<string> Recurse = (sDir) => {
foreach (var S in System.IO.Directory.GetDirectories(sDir)) {
Recurse(S);
}
};
将无法工作,因为 "Recurse
" 在定义之前就被使用了。
(也许你可以禁用此编译器错误,我不知道这是否被推荐。)
让我们利用局部递归的优势
private void GetDirInfo(string Root) {
int dirCount = 0;
int fileCount = 0;
long sizeSum = 0;
Action<DirectoryInfo> Recurse = null;
Recurse = (parent) => {
Console.WriteLine(parent.FullName);
var fileInfos = parent.GetFiles();
fileCount += fileInfos.Length;
foreach (var FI in fileInfos) {
sizeSum += FI.Length;
}
var children = parent.GetDirectories();
dirCount += children.Length;
foreach (var DI in children) {
Recurse(DI);
}
};
Recurse(new DirectoryInfo(Root));
Console.WriteLine(string.Concat(
"dirCount: ", dirCount, "\tfileCount: ",
fileCount, "\tsizeSum: ", sizeSum));
}
这个例子将信息收集到 3 个局部变量中。之后,它们可以被显示出来,无需任何额外操作。
使用匿名方法的线程处理
另一个问题是,如何将方法调用 "转移" 到一个副线程中。我使用 Delegate.BeginInvoke()
方法,该方法从线程池中获取线程。其他线程处理方式可能运行更快,或者可能对副线程进程提供更多控制 - 这种方法很简单,并且对系统资源很友好
private void toolStripButton2_Click(object sender, EventArgs e) {
Action action = () => GetDirInfo(@"..\..");
action.BeginInvoke(ar => action.EndInvoke(ar), null);
}
是的,这个例子很短,不是吗?它将同步调用转换为异步调用。
嗯...,现在我很抱歉...
...这个问题可以变得更简单,无需匿名方法
private void toolStripButton2_Click(object sender, EventArgs e) {
Action<string> action = new Action<string>(GetDirInfo);
action.BeginInvoke(@"..\..", action.EndInvoke, null);
}
(从这里开始,我对此文章做了一些修改。)
那个奇怪的 BeginInvoke 参数 (..., action.EndInvoke, ...)
是什么?
查看(使用 objectbrowser
)Action.BeginInvoke()
的签名
public virtual IAsyncResult BeginInvoke(System.AsyncCallback callback, object obj)
浏览查看什么是 "AsyncCallback
" - 一个委托,定义如下
public delegate void AsyncCallback(System.IAsyncResult ar)
BeginInvoke() 模式(请点击链接)要求我们实现一个具有该签名的单独方法,并将方法的地址传递给 BeginInvoke()
,以便副线程可以在其工作完成后回 call。为了 that,副线程将传递一个 IAsyncResult
(不管那是什么)。
通常我不需要这样做。我的线程函数 "知道" 它们何时运行完毕。但是(参见上面的链接)Microsoft 坚持要调用 EndInvoke
Important Note:
Always call EndInvoke to complete your asynchronous call.
并且 EndInvoke()
只能在副线程运行完毕后调用(否则它会阻塞主线程,直到副线程完成)。
现在观察 Action.EndInvoke()
的签名
public virtual void EndInvoke(System.IAsyncResult result)
是的,它 是 一个 AsyncCallback
!
因此 Action.EndInvoke
是 Action.BeginInvoke()
的有效参数。
再一个参数:(..., ..., null)
??
(在这一点上,我不得不说:我没有设计它。)[Delegate].BeginInvoke()
的最后一个参数是将数据传输给回调。该数据将作为 "result.State"
出现在传递给回调的 IAsyncResult
中,当副线程回调时。正如我之前所说:通常我不需要这样做。
结论
这两个问题都可以被视为一种 "模式":你可以将递归的东西应用于几乎所有递归需求。
对于线程处理问题也是如此:它可以转换任何 void
方法。(并且我会注意将我的线程处理程序设计为 void
方法)。
哦哦!
现在我 "完成了"(有可能完全完成任何编程问题吗?)我的文章,努力解释线程处理方面的内容。我偶然发现了这个:竞赛获胜者 "C# Jun 2006"
请点击该链接,以深入了解 .NET Framework 中的异步调用。