0

I'm trying to code a board game in c#, and right now the way the game is supposed to work is that the game mechanics are handled in a separate class (called GameBoard) from the GUI, and when a button in the GUI is pressed, it pulses, which is supposed to be detected by the GameBoard, which handles the action.

I've written a test to simulate this by running the "takeTurn" method in a thread, setting the static variable that indicates which card was clicked on, and sending the pulse, than waiting for a reply from the "takeTurn" to indicate that is is finished playing that card so that I can execute an Assert to verify that card I want played has been played. As far as I can tell, the thread is executing normally, instead of as a thread. I placed a print statement directly after thread.Start(), and it has not executed, the code is just hanging up on a wait statement (I think). this is the test code:

Thread t = new Thread(new ThreadStart(p1.actionPhase));
t.Start();
Console.WriteLine("TEST: thread launched successfully");
GameBoard.lastCardPlayed = new Witch();
Console.Write("about to enter sync block.");

lock (GameBoard.syncObject){
    Console.WriteLine("Entering sync block");
    Thread.Sleep(5000);
    Monitor.PulseAll(GameBoard.syncObject);
    Console.WriteLine("Button pressed!");
    Monitor.Wait(GameBoard.syncObject);
    Console.WriteLine("finished waiting.");
}
Assert.IsTrue(p2.getDiscard().Contains(new Witch()));

and here is the code I'm testing:

public override void actionPhase()
{
    lock (GameBoard.syncObject)
    {
        Console.WriteLine("PLAYER: Action Phase called on player " + getNumber());
        Monitor.Wait(GameBoard.syncObject);
        Console.WriteLine("PLAYER: Button pulse recieved.");
        Card cardPlayed = GameBoard.lastCardPlayed;
        playCard(cardPlayed);
        Monitor.PulseAll(GameBoard.syncObject);
        Console.WriteLine("PLAYER: finished playing card, pulse sent.");
        Console.WriteLine("Playing a card with ID " + cardPlayed.getID());
    }
}

The only thing output before the test hangs up is "Action Phase called on player 1"

icaughtfireonce
  • 171
  • 1
  • 10
  • 1
    Your approach is really out-dated, look into TAP https://msdn.microsoft.com/en-us/library/hh873175%28v=vs.110%29.aspx – Mikko Viitala May 15 '15 at 17:39
  • 1
    `Pulse` are not saved. If someone pulse while you not wait for it, then it got lost. – user4003407 May 15 '15 at 17:41
  • That's why I used Thread.Sleep, but the code's not even getting to execute any pulses. – icaughtfireonce May 15 '15 at 17:45
  • Just starting to read this TAP page, and I'm seeing the word "Asynchronous" tossed around a lot. I'm pretty sure I need to be synchronous. I'm not really to clear on what either thing means though... – icaughtfireonce May 15 '15 at 17:48

1 Answers1

2

You're calling lock on GameBoard.syncObject, so you're already blocking on the syncObject by the time you reach Monitor.Wait(GameBoard.syncObject);

You can think of

lock(GameBoard.syncObject)
{
    // Target Code
}

as equivalent to:

try 
{
    Monitor.Enter(GameBoard.syncObject);

    // Target Code
}
finally 
{
    Monitor.Exit(GameBoard.syncObject);
}

So why's that matter? If you step through it, you'll see that the sequence of events is something like:

  1. The actionPhase thread is started.
  2. The main thread acquires the syncObject via Monitor.Enter (implictly through the lock), and then sleeps.
  3. actionPhase then attempts to aquire the lock via Monitor.Enter (implicitly again). (Note that "PLAYER: Action Phase called on player" isn't printed until 5s seconds later.)
  4. The main thread wakes up from the sleep, and calls PulseAll, which places the main thread lock on the monitor wait queue as Eugene points out, and which allows the actionPhase to then aquire the lock on syncObject that it's been blocking on. (Related: On pulse vs enter)

But now both threads are calling Monitor.Wait, and no one remains to pulse. (Or release their locks, either explicitly or by exiting the lock {} scope.)

Does that make sense?


Mikko is right though, we're just explaining the bug - really the answer is to use a more current model. Task/Async is what you're after, and there's a wealth of resource out there. Look into Task.Run or one of the many Task/Async guides to get you started - it's time well spent.

Community
  • 1
  • 1
Gene
  • 1,587
  • 4
  • 18
  • 38
  • 1
    What does Enter and Exit do? – icaughtfireonce May 15 '15 at 17:45
  • 1
    @Gene By the time it reaches `Monitor.Wait(GameBoard.syncObject);` it adds itself to the special monitor queue and relinquishes control over the sync object. Now it has to wait for `Pulse` or `PulseAll` from other thread to reobtain the lock, but only after that other thread also calls `Monitor.Exit`. So this explanation unfortunately doesn't explain the problem. You can read more about Pulse [here](http://www.codeproject.com/Articles/28785/Thread-synchronization-Wait-and-Pulse-demystified) or on MSDN. – Eugene Podskal May 15 '15 at 18:09
  • @Eugene You're right of course. That was careless of me, will update! – Gene May 15 '15 at 18:38
  • Minor nitpicking : "calls PulseAll, which places the main thread lock on the monitor queue" - you probably meant Wait, because PulseAll essentially prepares monitor to do the opposite. – Eugene Podskal May 15 '15 at 19:06
  • And not so minor: "You're calling lock on GameBoard.syncObject, so you're already blocking on the syncObject by the time you reach Monitor.Wait(GameBoard.syncObject);" contradicts your own elaboration later. I'd rather see whole section before sequence explanation removed or replaced with some short explanation and link to http://stackoverflow.com/questions/6029804/how-does-lock-work-exactly. – Eugene Podskal May 15 '15 at 19:10