473,396 Members | 1,827 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,396 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 1817
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:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

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.