65.9K
CodeProject is changing. Read more.
Home

Local Recursion with Anonymous Methods and Simple Threading

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (13 votes)

Jul 12, 2008

CPOL

3 min read

viewsIcon

35007

Anonymous methods help in keeping the "environment" of recursive processing encapsulated

Introduction

Recursive functions can get a little complex when dealing with several vars. Either these vars must become class-members (violating the rule of encapsulation) or they must be passed as parameters through all the recursive (self-)calls.

A local recursion can work on the local vars of the surrounding method.

Here We Go

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(@"..\..");
}

That's the basic one, just to show the principle: create an anonymous Action, then call it.
The only unusual feature is: you can't declare and initiate in one line.

Action<string> Recurse = (sDir) => {
    foreach (var S in System.IO.Directory.GetDirectories(sDir)) {
        Recurse(S);
    }
};

will not work, because "Recurse" is used, before it is defined.

(Maybe you can disable this compiler-error, I don't know whether that's recommended.)

Let's Use the Benefits of Local Recursion

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));
}  

This one collects information into 3 local vars. After that they can be displayed, without efforts.

Threading with Anonymous Methods

The other issue is, how to "transfer" a method-call into a side-thread. I use the Delegate.BeginInvoke() method, which takes threads from the threadpool. Other ways of threading may run faster, or may give more control on the side-thread-process - this approach is simple and is kind to system-resources:

private void toolStripButton2_Click(object sender, EventArgs e) {
   Action action = () => GetDirInfo(@"..\..");
   action.BeginInvoke(ar => action.EndInvoke(ar), null);
}

Yeah, this one is pretty short, isn't it? It translates the synchronous call to an asynchronous one.

Ähm..., and Now I'm Sorry...

... that issue can be made still more simple, without anonymous methods:

private void toolStripButton2_Click(object sender, EventArgs e) {
   Action<string> action = new Action<string>(GetDirInfo);
   action.BeginInvoke(@"..\..", action.EndInvoke, null);
} 

(From here on, I've made some changes to this article.)

What's that strange BeginInvoke-Parameter (..., action.EndInvoke, ...)??

Look (with the objectbrowser) at Action.BeginInvoke()s Signature:

public virtual IAsyncResult BeginInvoke(System.AsyncCallback callback, object obj)

Browse on to see, what an "AsyncCallback" is - a delegate, defined as follows:

public delegate void AsyncCallback(System.IAsyncResult ar)

The BeginInvoke()-Pattern (please follow the link) wants us to implement a separate method with that signature, and pass the methods address to BeginInvoke(), for that the side-thread can call back, when its job is done. To that Callback-Method the side-thread will pass an IAsyncResult (whatever that may be).

Usually I don't need that. My threaded functions "know" themselves, when they run out. But (see the link above) Microsoft insists on calling EndInvoke:

 Important Note: 
Always call EndInvoke to complete your asynchronous call. 

And EndInvoke() can only be called after the side-thread has run out (otherwise it blocks the main-thread, until the side-thread finishes).

Now watch the signature of Action.EndInvoke():

public virtual void EndInvoke(System.IAsyncResult result) 

Yeah, it is an AsyncCallback!
And for that Action.EndInvoke is a valid argument for Action.BeginInvoke().

One more param: (..., ..., null)??

(At this point, I have to say: I didn't design that.)
The last param of [Delegate].BeginInvoke() is to transfer data to the callback. That data will appear as "result.State" in the IAsyncResult, which is passed to the callback, when the side-thread calls back. As I said before: usually I don't need that.

Conclusion

Both issues can be assumed as a kind of "pattern": You can apply the recursion-stuff to mostly all recursive requirements.

Same to the threading-issue: It can translate any void method. (And I will take care to design my threading-stuff as void methods).

Ooooh!

Now I've "finished" (is it possible to finish any programming-issue at all?) my article, trying hard to explain that threading-stuff. I stumbled over this: Competition-winner "C# Jun 2006"

Follow that link, to get a deep understanding about asynchronous calls in .NET-Framework.