Just a quick little note that I'm at the PDC conference. I'll be at the multicore/concurrent programming pre-con session. If that's where you'll be as well, be sure to say hi.
Sunday, October 26, 2008
Wednesday, October 15, 2008
Value Capture vs. Variable Capture
Anonymous methods (aka. Closures) capture the variables from the outer scope that are referenced within the body of the method itself. This is the intended behavior as it serves to extend the lifetime of the variable to match that of the anonymous method itself. This should not be confused with capturing the value of a variable. In my previous posts, A Sink Programming and More A Sink Kronos Programming, I demonstrated a technique for asynchronously dispatching an anonymous method from a background thread into the main UI thread. I also mentioned how there was a potential race-condition on the SR local variable. The simple, yet not very scalable, way of eliminating this race was to pause the loop by calling EndInvoke() prior to accessing SR again (in the FindNext() call). This time, I'm going to show a technique for capturing the value of the SR.Name field and not the whole SR variable. This will eliminate the race on the SR variable because the anonymous method body will no longer need to access it.
Value capturing is a little more manual, but through the use of generic methods and a corresponding generic class, we only need to create this code once. The idea is to add an overloaded BeginInvoke() method that takes an extra parameter, which will be the value we want to capture and pass along to the anonymous method. Here's the new BeginInvoke method:
TControlHelper = class helper for TControl
...
function BeginInvoke<T1>(const AProc: TProc<T1>; const Param: T1): IASyncResult; overload;
...
end;
This overload is different from the BeginInvoke<TResult> version since the AProc parameter takes a procedure reference instead of a function reference. As you can see, the procedure reference (can be an anonymous method), AProc, is defined as taking a single parameter of type T1. There is also an extra parameter which is where we'll pass in the value we want to capture. Behind the scenes we'll need to save off this value some place so when the procedure is called, that value can be passed along. For this, we'll create another descendant of the TBaseAsyncResult class, only this time it is a generic class because we need to have a field of type T1:
TAsyncProcedureResult<T1> = class sealed (TBaseAsyncResult)
private
FAsyncProcedure: TProc<T1>;
FParam: T1;
protected
procedure AsyncDispatch; override;
constructor Create(const AAsyncProcedure: TProc<T1>; const Param: T1);
end;
As before, we override the AsyncDispatch abstract virtual method and add a constructor that takes the procedure reference and the value. The body of BeginInvoke<T1>() looks like this:
function TControlHelper.BeginInvoke<T1>(const AProc: TProc<T1>; const Param: T1): IASyncResult;
begin
Result := TAsyncProcedureResult<T1>.Create(AProc, Param).Invoke;
end;
Now let's change the background thread code to take advantage of this:
procedure TBeginInvokeTestForm.TSearchThread.Execute;
var
SR: TSearchRec;
SH: Integer;
AR: IAsyncResult;
begin
if not Terminated then
begin
AR := FForm.ListBox1.BeginInvoke<string>(TFunc<string>(function: string
begin
Result := FForm.Edit1.Text;
end));
FFolder := FForm.ListBox1.EndInvoke<string>(AR);
SH := FindFirst(IncludeTrailingPathDelimiter(FFolder) + '*.*', faAnyFile, SR);
while (SH = 0) and not Terminated do
begin
//Sleep(10); // this makes the background thread go a little slower.
AR := FForm.ListBox1.BeginInvoke<string>(TProc<string>(procedure (SRName: string)
begin
if not Terminated then
FForm.ListBox1.Items.Add(SRName);
end),
SR.Name); // Pass the value of SR.Name on through.
// FForm.ListBox1.EndInvoke(AR); { this call can be safely removed since SR isn't
touched inside the anonymous method body}
SH := FindNext(SR);
end;
end;
end;
Now the the loop in this thread will execute as quickly as it can to dispatch asynchronous calls to the main UI thread regardless of how fast they can be consumed. This same technique can be applied to the "TFunc" version by adding more BeginInvoke() overloads. It can also be extended to allow more than one extra parameter so you can capture and pass long many values. In the above case, it only specifically captured just the string that is the name of the file. It could have also captured the value of the whole SR structure.
What about exceptions? What if an exception were raised while the anonymous method were executing? It would still get caught and propagated back to the specific IAsyncResult instance but because EndInvoke() isn't being called it is lost in the ether. It is also "bad form" to forgo the call to EndInvoke() as there may be some other internal cleanup that needs to happen. A simple way to deal with this is to store the IAsyncResult instances in a local TList<IAsyncResult> list. Then once the loop is done, iterate through the list, calling EndInvoke() on each one. This may still be less than ideal because you cannot cancel subsequent invocations of the anonymous method which may just stack up a whole plethora of exceptions. In this instance, aside from the exception problem, it is ok to defer or not call EndInvoke(). In other cases this may not be true, such as using this technique for overlapped IO.
Friday, October 10, 2008
CodeRage III - PDC - Delphi Programming
- Be sure to mark your calendar for the upcoming CodeRage virtual conference. Apparently the abstract submissions are coming in fast and furious, so if you would like to present, make sure you contact Anders Ohlsson.
- There are several folks in the Delphi community planning on taking advantage of the whole Open Space or UnSessions thing at PDC this year. Since I'll be there this year, let me know if you're planning an UnSession and what it'll be about; I may show up. Also, if there is some kind of Delphi/C++Builder related UnSession you'd like to see, let me know too and it may happen.
- Delphi Programming - Just doing my part.
- I just realized that I have written nearly 450 posts in the 5 years I've been doing this whole blog thing. I hope I haven't wasted too much of your time if you've been following along.
Monday, October 6, 2008
Some newsworthy items
- In case you've missed it, while at the SDN conference in the Netherlands, Nick Hodges has dropped some information about Delphi Prism, the next release of Delphi on the .NET platform hosted inside the Visual Studio Shell. Dr. Bob, Marco Cantu and Tim Anderson, have all commented on it. I guess this isn't an "official" announcement as that will happen later this month at the Microsoft PDC.
- Speaking of the Microsoft PDC, I'll be there. If you're planning on going and would like to meet, let me know.
- We've been reworking our beta-test process using a new solution from Centercode.
- If you'd like to get in on the Delphi Prism fun and try out the new Centercode portal at the same time, you can apply to be a beta tester here.
- The Scotts Valley High School Falcons, SCCAL co-champions, beat reigning CCS champion, the Santa Cruz High School Cardinals under the Friday night lights in both teams' league opener, October 3rd, 2008. Scotts Valley is now 1-0 in league play, 4-1 overall. Why does this matter? My son is a starting lineman for the Scotts Valley Falcons :-). Next Saturday, October 11th, Scotts Valley travels to Harbor High School to meet up with the Pirates on their home turf. Game at 2pm, PDT.