65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (13投票s)

2008年7月12日

CPOL

3分钟阅读

viewsIcon

35363

匿名方法有助于保持递归处理的 "环境" 被封装

引言

处理多个变量时,递归函数可能会变得稍微复杂。这些变量要么必须成为类成员(违反封装规则),要么必须作为参数通过所有递归(自)调用传递。

局部递归可以在周围方法的局部变量上工作。

我们开始吧

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, ...) 是什么?

查看(使用 objectbrowserAction.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.EndInvokeAction.BeginInvoke() 的有效参数。

再一个参数:(..., ..., null)??

(在这一点上,我不得不说:我没有设计它。)
[Delegate].BeginInvoke() 的最后一个参数是将数据传输给回调。该数据将作为 "result.State" 出现在传递给回调的 IAsyncResult 中,当副线程回调时。正如我之前所说:通常我不需要这样做。

结论

这两个问题都可以被视为一种 "模式":你可以将递归的东西应用于几乎所有递归需求。

对于线程处理问题也是如此:它可以转换任何 void 方法。(并且我会注意将我的线程处理程序设计为 void 方法)。

哦哦!

现在我 "完成了"(有可能完全完成任何编程问题吗?)我的文章,努力解释线程处理方面的内容。我偶然发现了这个:竞赛获胜者 "C# Jun 2006"

请点击该链接,以深入了解 .NET Framework 中的异步调用。

© . All rights reserved.