473,327 Members | 1,896 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,327 software developers and data experts.

Anonymous Delegates, captured variables and ThreadStart

Hi,

I recently came to debug some old code that was causing a
StackOverflowException. The code in question makes significant use of
recursion and with large data structures it exhausted the stack. As
the code is complicated and would take significant re-testing it
wasn't practical to re-write it at present.

As a work-around until the code could be re-written, I decided to
invoke it on a new thread to which I had allocated a larger stack, and
then block the calling thread until the work was complete. The code I
wrote was like follows:

----8<----

MyResult[] results;
Exception ex;
ThreadStart ts = new ThreadStart(
delegate
{
try
{
results = DoWork();
}
catch (Exception threadEx)
{
ex = threadEx;
}
}
);

Thread t = new Thread(ts, STACKSIZE);
t.Start();
t.Join();

if (ex != null)
{
// etc...
}

foreach (MyResult result in results)
{
// etc...
}

----8<----

This fixed the StackOverflowException and appeared to work. A short
while later it occurred to me that this approach was not thread-safe.

It's easy to look at that code and read it as if you're only using
local variables, but in fact you are using a public class member
variable in a class generated by the compiler for the anonymous
delegate. Whilst there's nothing to synchronise as such because the
main thread is blocked waiting, you *do* need a memory barrier to be
thread-safe.

To prove my theory I constructed the following test case. On my
machine, with a Release build and no debugger attached, the while loop
never exits as the hasFinished variable gets enregistered and so never
sees the update from the other thread.

----8<----

using System;
using System.Threading;

namespace ThreadObserver
{
class Program
{
static void Main(string[] args)
{
bool hasFinished = false;

ThreadStart ts = new ThreadStart(
delegate
{
Thread.Sleep(5 * 1000);
hasFinished = true;
}
);

Thread t = new Thread(ts);
t.Start();

while (!hasFinished)
{
}

Console.WriteLine("Finished.");
Console.Read();
}
}
}

----8<----

It's actually fairly easy to fix, even still using anonymous delegates
- just make sure you capture a lock object to use when reading/writing
the return value(s). The following code always exits successfully:

----8<----

using System;
using System.Threading;

namespace ThreadObserver
{
class Program
{
static void Main(string[] args)
{
object myLock = new object();
bool hasFinished = false;

ThreadStart ts = new ThreadStart(
delegate
{
Thread.Sleep(5 * 1000);
lock (myLock)
{
hasFinished = true;
}
}
);

Thread t = new Thread(ts);
t.Start();

bool fin;
do
{
lock (myLock)
{
fin = hasFinished;
}
} while (!fin);

Console.WriteLine("Finished.");
Console.Read();
}
}
}

----8<----

This is probably an unusual case, but thought this would be worth
posting in case anyone else has used a similar pattern or is thinking
of doing so.

It highlighed to me just how necessary it is to keep remembering what
the compiler is and isn't doing for you when you use convenient
language features such as anonymous delegates.

Regards,

Matt
Jun 27 '08 #1
1 1816
On Jun 13, 5:22 pm, ktrvnb...@sneakemail.com wrote:
Hi,

I recently came to debug some old code that was causing a
StackOverflowException. The code in question makes significant use of
recursion and with large data structures it exhausted the stack. As
the code is complicated and would take significant re-testing it
wasn't practical to re-write it at present.

As a work-around until the code could be re-written, I decided to
invoke it on a new thread to which I had allocated a larger stack, and
then block the calling thread until the work was complete. The code I
wrote was like follows:

----8<----

MyResult[] results;
Exception ex;
ThreadStart ts = new ThreadStart(
delegate
{
try
{
results = DoWork();
}
catch (Exception threadEx)
{
ex = threadEx;
}
}
);

Thread t = new Thread(ts, STACKSIZE);
t.Start();
t.Join();

if (ex != null)
{
// etc...

}

foreach (MyResult result in results)
{
// etc...

}

----8<----

This fixed the StackOverflowException and appeared to work. A short
while later it occurred to me that this approach was not thread-safe.

It's easy to look at that code and read it as if you're only using
local variables, but in fact you are using a public class member
variable in a class generated by the compiler for the anonymous
delegate. Whilst there's nothing to synchronise as such because the
main thread is blocked waiting, you *do* need a memory barrier to be
thread-safe.
I'm not at all sure you do, because of your call to Join().

I can't possibly see how that would work without a memory barrier, and
the writes have to happen before the previous thread terminates so
there's an effective memory barrier there.
To prove my theory I constructed the following test case.
That code doesn't prove that your original code was problematic - your
test case doesn't call Thread.Join. Show me a case where you call
Join() but *then* don't see the most recently written value and I'll
admit you've got a problem.

I can't point to any documentation in Join() that proves there's
effectively a memory barrier in the calling thread after the target
thread has exited, but I would be very, very surprised if this were
not the case.

I can mail Joe Duffy and see if he has a definitive answer though, if
you'd like.

Jon
Jun 27 '08 #2

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

3
by: anonymous | last post by:
I believe I ran into an interesting way to create memory leaks in C# 2.0 using anymous delegates. Here is a sample of the code in question. private void Handle_Event(object sender, EventArgs e)...
5
by: cody | last post by:
I have a very funny/strange effect here. if I let the delegate do "return prop.GetGetMethod().Invoke(info.AudioHeader, null);" then I get wrong results, that is, a wrong method is called and I...
7
by: d225563 | last post by:
I read an article that had a really elegant solution to pass parameters to a thread by using an anonymous method as your ThreadStart. It seemed pretty slick and even worked when I tried it. ...
15
by: Matt | last post by:
Hi There, Can anyone explain me the real advantages of (other than syntax) lambda expressions over anonymous delegates? advantage for one over the other. delegate int F(int a); F fLambda = a...
4
by: Frankie | last post by:
I have just gotten up to speed on what anonymous methods are (syntax, capabilities, etc), and how they can be used with /called via delegates. What I am wondering is... 1. Are they only/mostly...
2
by: Gabe Moothart | last post by:
Hello, In one of my asp.net applications, I create a series of checkboxes, set their properties, and give them an "onChecked" event handler on the fly using an anonymous method. The code looks...
1
by: robparr | last post by:
Hello, hopefully someone can help me. I am reading and learning steadily about threading and asynchronous programming, which to me sound like the same thing. At the moment I am not sure what the...
2
by: Tony | last post by:
Hello! Here I have some text from a book I read. It says: "An interesting point to note concerning anonymous methods is that they are effectively local to the code block that contains them, and...
0
by: Peter Duniho | last post by:
On Mon, 01 Sep 2008 16:14:10 -0700, Blip <blip@krumpli.comwrote: Briefly, an anonymous method is exactly that: a method without a name. When you use the "delegate" keyword to declare an...
0
by: ryjfgjl | last post by:
ExcelToDatabase: batch import excel into database automatically...
1
isladogs
by: isladogs | last post by:
The next Access Europe meeting will be on Wednesday 6 Mar 2024 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM). In this month's session, we are pleased to welcome back...
0
by: Vimpel783 | last post by:
Hello! Guys, I found this code on the Internet, but I need to modify it a little. It works well, the problem is this: Data is sent from only one cell, in this case B5, but it is necessary that data...
0
by: jfyes | last post by:
As a hardware engineer, after seeing that CEIWEI recently released a new tool for Modbus RTU Over TCP/UDP filtering and monitoring, I actively went to its official website to take a look. It turned...
0
by: ArrayDB | last post by:
The error message I've encountered is; ERROR:root:Error generating model response: exception: access violation writing 0x0000000000005140, which seems to be indicative of an access violation...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
1
by: Defcon1945 | last post by:
I'm trying to learn Python using Pycharm but import shutil doesn't work
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.