Method calls in .NET multithreading

I

Ioannis Vranos

In .NET multithreading we have to assign a thread to a method of a
separate object each time (and not two methods of the same object)?


In other words, why does this hung?


#using <mscorlib.dll>

using namespace System;
using namespace System::Threading;


class SomeException
{};


__gc class SomeClass
{
int index;

//...

public:

// ...


void DoSomething()
{
Monitor::Enter(this);

throw SomeException();

// Modify index

Monitor::Exit(this);
}

void DoSomethingElse()
{
Monitor::Enter(this);

// Modify index

Monitor::Exit(this);
}

// ...
};


int main() try
{
SomeClass *ps= __gc new SomeClass;


Thread *pthread1= __gc new Thread ( __gc new ThreadStart(ps,
&SomeClass::DoSomething) );

Thread *pthread2= __gc new Thread ( __gc new ThreadStart(ps,
&SomeClass::DoSomethingElse) );


//Start execution of ps->DoSomething()
pthread1->Start();

//Start execution of ps->DoSomethingElse()
pthread2->Start();
}

catch(SomeException)
{
Console::WriteLine("SomeException caught in main thread!\n");
}
 
I

Ioannis Vranos

Ioannis said:
In .NET multithreading we have to assign a thread to a method of a
separate object each time (and not two methods of the same object)?

In other words, why does this hang?


I had pasted wrong code. Here is the correct one:


#using <mscorlib.dll>

using namespace System;
using namespace System::Threading;

class SomeException
{};

__gc class SomeClass
{
int index;

//...

public:

// ...


void DoSomething() try
{
Monitor::Enter(this);

throw SomeException();

// Modify index

Monitor::Exit(this);
}

==> Handle inside the specific thread locally
catch(SomeException)
{
// ...
}

void DoSomethingElse()
{
Monitor::Enter(this);

// Modify index

Monitor::Exit(this);
}

// ...
};


int main() try
{
SomeClass *ps= __gc new SomeClass;


Thread *pthread1= __gc new Thread ( __gc new ThreadStart(ps,
&SomeClass::DoSomething) );

Thread *pthread2= __gc new Thread ( __gc new ThreadStart(ps,
&SomeClass::DoSomethingElse) );


//Start execution of ps->DoSomething()
pthread1->Start();

//Start execution of ps->DoSomethingElse()
pthread2->Start();
}

catch(Exception *pe)
{
Console::WriteLine("{0}", pe->Message);
}
 
D

Doug Harrison [MVP]

In other words, why does this hang?


I had pasted wrong code. Here is the correct one:


#using <mscorlib.dll>

using namespace System;
using namespace System::Threading;

class SomeException
{};

__gc class SomeClass
{
int index;

//...

public:

// ...


void DoSomething() try
{
Monitor::Enter(this);

throw SomeException();

// Modify index

Monitor::Exit(this);
}

==> Handle inside the specific thread locally
catch(SomeException)
{
// ...
}

void DoSomethingElse()
{
Monitor::Enter(this);

// Modify index

Monitor::Exit(this);
}

// ...
};


int main() try
{
SomeClass *ps= __gc new SomeClass;


Thread *pthread1= __gc new Thread ( __gc new ThreadStart(ps,
&SomeClass::DoSomething) );

Thread *pthread2= __gc new Thread ( __gc new ThreadStart(ps,
&SomeClass::DoSomethingElse) );


//Start execution of ps->DoSomething()
pthread1->Start();

//Start execution of ps->DoSomethingElse()
pthread2->Start();
}

catch(Exception *pe)
{
Console::WriteLine("{0}", pe->Message);
}

I can't get this to hang.

You're starting two threads to run functions on the object ps, which is
fine in and of itself. However, the function DoSomething locks ps, throws
an exception, and apparently fails to unlock ps. Provided the thread goes
on to exit, I believe this will abandon the sync object associated with ps,
making it available to DoSomethingElse. If for some reason DoSomething
doesn't exit, then there's the potential for DoSomethingElse to wait
forever, and your program will hang. The solution to that is to unlock
"this" inside your catch block. (Of course, this all assumes the threads
execute in the order they're created.)
 
I

Ioannis Vranos

Doug said:
I can't get this to hang.


Strangely enough, it does not hang always. Run it sometimes and you will
see it hanging. I can not understand why it does not hang all the time
though (since Monitor::Exit() is not called in the catch block).


You're starting two threads to run functions on the object ps, which is
fine in and of itself. However, the function DoSomething locks ps, throws
an exception, and apparently fails to unlock ps. Provided the thread goes
on to exit, I believe this will abandon the sync object associated with ps,
making it available to DoSomethingElse. If for some reason DoSomething
doesn't exit, then there's the potential for DoSomethingElse to wait
forever, and your program will hang. The solution to that is to unlock
"this" inside your catch block. (Of course, this all assumes the threads
execute in the order they're created.)


Yes, Monitor::Exit(this); inside the catch block makes the program to
never hang. However why it hangs sometimes and sometimes it doesn't,
without this?
 
D

Doug Harrison [MVP]

Strangely enough, it does not hang always. Run it sometimes and you will
see it hanging. I can not understand why it does not hang all the time
though (since Monitor::Exit() is not called in the catch block).

FWIW. I ran it 20 times in a row, and it never hung. Aren't MT problems
fun?
Yes, Monitor::Exit(this); inside the catch block makes the program to
never hang. However why it hangs sometimes and sometimes it doesn't,
without this?

It turns out I was wrong. Unlike a mutex, the SyncBlock associated with an
object is not abandoned when a thread exits while holding it. However, I
was on the right track when I alluded to order issues. Provided thread 1
runs before thread 2, locks the object, and exits, thread 2 will block
forever on its Monitor.Enter call. To make it do so (almost)
deterministically, insert the following at the start of DoSomethingElse:

Thread::Sleep(1000);

This should give thread 1 enough time to do its thing and mess up thread 2.
 
I

Ioannis Vranos

Doug Harrison [MVP] wrote:

FWIW. I ran it 20 times in a row, and it never hung. Aren't MT problems
fun?


I compiled it from command line, with cl /clr /EHsc temp.cpp and no
other switch. /EHsc is important.

It turns out I was wrong. Unlike a mutex, the SyncBlock associated with an
object is not abandoned when a thread exits while holding it. However, I
was on the right track when I alluded to order issues. Provided thread 1
runs before thread 2, locks the object, and exits, thread 2 will block
forever on its Monitor.Enter call. To make it do so (almost)
deterministically, insert the following at the start of DoSomethingElse:

Thread::Sleep(1000);

This should give thread 1 enough time to do its thing and mess up thread 2.


Does it hang to you with this way? :)
 
J

Jochen Kalmbach

Hi Ioannis,
void DoSomething()
{
Monitor::Enter(this);

throw SomeException();

// Modify index

Monitor::Exit(this);
}

The abouve code will always forget to call the "Exit" !


Normally you should *always* do:

Monitor::Enter(this);
__try
{
}
__finally
{
Monitor::Exit(this);
}


--
Greetings
Jochen

My blog about Win32 and .NET
http://blog.kalmbachnet.de/
 
D

Doug Harrison [MVP]

Doug Harrison [MVP] wrote:


Does it hang to you with this way? :)

Yes. I said "almost" deterministically because it's using Sleep to fake out
the scheduler, a practice that isn't guaranteed to work but does well
enough to demonstrate the problem.

Aside: It's interesting to note that by using the Thread class, your
example illustrated the use of foreground threads. Had you used background
threads, or if you were to set the IsBackground property on the threads,
your program would have terminated right away, because extant background
threads don't keep a program alive once the primary thread has exited. Only
foreground threads do that. In general, you should join with your secondary
threads before exiting your primary thread.
 
I

Ioannis Vranos

Doug said:
Yes. I said "almost" deterministically because it's using Sleep to fake out
the scheduler, a practice that isn't guaranteed to work but does well
enough to demonstrate the problem.

Aside: It's interesting to note that by using the Thread class, your
example illustrated the use of foreground threads. Had you used background
threads, or if you were to set the IsBackground property on the threads,


.... the only way that I know for creating .NET background threads. Is
there another?

your program would have terminated right away, because extant background
threads don't keep a program alive once the primary thread has exited.


Yes indeed.


Only
foreground threads do that. In general, you should join with your secondary
threads before exiting your primary thread.


You mean to join the primary thread and secondary threads all together?
However this doesn't sound much multithreading. :)


Something else based on this, is it better to join dependent threads -
whenever possible- instead of using locks? What would this mean for
performance in multi-cpu/core environments? Is this a way to avoid the
thread lock mess, while sacrificing almost no speed?
 
D

Doug Harrison [MVP]

... the only way that I know for creating .NET background threads. Is
there another?

Thread pool and by extension asynchronous delegates. In addition, unmanaged
threads that call into managed code are marked as background threads.
You mean to join the primary thread and secondary threads all together?
However this doesn't sound much multithreading. :)

But if your program is shutting down, it's usually undesirable to allow
your secondary threads to continue to run as the larger environment in
which they're executiing is going away. To conduct an orderly shutdown,
your primary thread has to ask all your secondary threads to exit and join
with them first.
Something else based on this, is it better to join dependent threads -
whenever possible- instead of using locks?

Not sure what you mean. By "join" I mean wait for the thread to exit.
What would this mean for
performance in multi-cpu/core environments? Is this a way to avoid the
thread lock mess, while sacrificing almost no speed?

Google on "lock-free programming". It's a hot topic these days.
 
C

Carl Daniel [VC++ MVP]

Doug said:
Not sure what you mean. By "join" I mean wait for the thread to exit.

To the OP:

join is not rendezvous (in the Ada sense). You can only join a thread when
it terminates (the pattern and terminology comes from Java most directly,
but dates to long before Java was born).

-cd
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top