Dispatcher.beginInvoke only executing the last time through a loop?
I've got a piece of code in a Silverlight code-behind that looks something like this:
foreach (MapLocation loc in e.Result)
{
testDict[loc.ElemId] = loc.ToString();
this.Dispatcher.BeginInvoke(delegate()
{
Image icon = new Im开发者_运维技巧age();
icon.SetValue(Image.SourceProperty, nurseIconSource);
Canvas.SetLeft(icon, (double)loc.X * MAP_SCALE);
Canvas.SetTop(icon, MAP_HEIGHT - (double)loc.Y * MAP_SCALE);
icons[loc.ElemId] = icon;
MainCanvas.Children.Add(icon);
});
}
}
This loop is running 25 times on a thread separate from the UI thread. The testDict object ends up with all 25 entries after execution of the method, while the icons dictionary only stores an entry for the 25th (last) item.
This is my first time using Dispatcher. Is it not meant to be called rapid-fire like this? All I can think of is that the first time the delegate is invoked is after the last time through the loop, so the loc object is always the same item. Is this accurate?
It's the old "don't capture a loop variable" problem. This catches a lot of people out.
Basically, when the delegate executes it's always using the current value of loc
... and if you've already finished the loop before the delegates execute, that'll mean the last value of loc
is displayed many times, basically.
The solution is to take a copy of the loop variable:
foreach (MapLocation loc in e.Result)
{
MapLocation copy = loc;
testDict[loc.ElemId] = loc.ToString();
this.Dispatcher.BeginInvoke(delegate()
{
Image icon = new Image();
icon.SetValue(Image.SourceProperty, nurseIconSource);
Canvas.SetLeft(icon, (double)copy.X * MAP_SCALE);
Canvas.SetTop(icon, MAP_HEIGHT - (double)copy.Y * MAP_SCALE);
icons[copy.ElemId] = icon;
MainCanvas.Children.Add(icon);
});
}
Note the use of "copy" instead of "loc" inside the anonymous method.
For more details, read Eric Lippert's blog posts on "Closing over the loop variable considered harmful" - part one; part two.
You are closing over an iteration variable. The solution is to assign loc to a temporary variable inside the loop.
foreach (MapLocation location in e.Result)
{
//assign temp variable here.
MapLocation loc = location;
//...
}
精彩评论