0

I'm trying to use a thread multiple times and have the thread stop processing if the user doesn't react fast enough.

 Thread ask = new Thread (new ThreadStart (MathQuiz.prompt));
ask.Start();
ask.Join(30000);
if (answer == 4)
{
    score = score+1;
    answer = 0;
}
Console.WriteLine();
Console.WriteLine("Press any key to move on to the next question!");
Console.WriteLine();
Console.ReadKey(true);
Console.WriteLine("What is 15 / 3?");
Console.WriteLine();
ask.Start();
ask.Join(30000);
if (answer == 5)
{
    score = score+1;
    answer = 0;
}

...

  static void prompt()
  {
    preanswer = (Console.ReadLine());
    if (!decimal.TryParse(preanswer, out answer))
        {
            Console.WriteLine("That wasn't even a number or decimal!");
        }
    else
        {
            answer = decimal.Parse(preanswer);
        }
  }

Right now it doesn't seem that the "prompt" thread is terminating, and so it crashes when the second question begins.

So I need a solution! I of course don't mind to answer questions to help myself get an answer.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Ian Cordle
  • 206
  • 4
  • 13
  • You might want to look at this question, for a solution which I think is relevant: http://stackoverflow.com/questions/142826/is-there-a-way-to-indefinitely-pause-a-thread/143153#143153 – blueberryfields Jan 21 '11 at 01:08

4 Answers4

2

The method Thread.Join(Int32) doesn't stop the other thread after the given number of milliseconds. It just stops waiting. It returns true if the other thread has terminated.

So if ask.Join(30000); returns false, the other thread is still running and you have to Abort the thread yourself.

Elian Ebbing
  • 18,779
  • 5
  • 48
  • 56
0

What evidence do you have that the prompt thread is not terminating? The main thread should be waiting for the prompt thread to terminate at the "join" places, so, if execution continues past a join, the "prompt" thread terminated.

blueberryfields
  • 45,910
  • 28
  • 89
  • 168
0

Console.ReadLine blocks the current thread from being aborted until the line has been read. (As per comment from Timwi)

To work around this, you would have to use the Console.KeyAvailable method here: How to add a Timeout to Console.ReadLine()?

I re-wrote the problem before realising my mistake, and now here's a solution.

It's rather more involved than I wanted it to be. (Using the KeyAvailable means I need to queue up the keys entered, and to support backspace I have to remove the items. I also need to sleep while no keys are available... )

private static AutoResetEvent answered = new AutoResetEvent(false);
private static Func<string, bool> questionCorrect = null;
private static bool? correct;

static void Main(string[] args)
{
    int score = 0;
    AskQuestion(ref score,
                "What is 15 / 3?",
                TimeSpan.FromSeconds(5),
                answer =>
                    {
                        decimal value;
                        if (!decimal.TryParse(answer, out value))
                        {
                            Console.WriteLine(
                                "That was not a valid number");
                            return false;
                        }

                        return (value == 15/3);

                    });


    AskQuestion(ref score,
                "What is 20  * 2 ?",
                TimeSpan.FromSeconds(5),
                answer =>
                    {
                        decimal value;
                        if (
                            !decimal.TryParse(answer,
                                              out value))
                        {
                            Console.WriteLine(
                                "That was not a valid number");
                            return false;
                        }

                        return (value == 20*2);

                    });


    Console.WriteLine("Done. Score: {0}", score);
    Console.ReadLine();
}

private static void AskQuestion(ref int score, string question, TimeSpan duration, Func<string, bool> validator)
{
    // Setup
    questionCorrect = validator;
    correct = null;
    answered.Reset();

    // Ask 
    Console.WriteLine(question);
    Thread thread = new Thread(GetQuestion);
    thread.Start();

    // Wait
    answered.WaitOne(duration);
    thread.Abort();
    thread.Join();

    Console.WriteLine(); // Write empty line, otherwise this overwrites the answer. 

    // Validate);
    if (correct.HasValue && correct.Value == true)
    {
        score++;
        Console.WriteLine("Correct");
    }
    else if (correct.HasValue)
    {
        Console.WriteLine("Incorrect");
    }
    else
    {
        Console.WriteLine("Timeout");
    }

}

private static void GetQuestion()
{
    try
    {
        List<char> captured = new List<char>();
        bool answerCaptured = false; 
        while (!answerCaptured)
        {
            while (Console.KeyAvailable)
            {

                var key = Console.ReadKey();
                if (key.KeyChar == '\r' || key.KeyChar == '\n')
                {
                    answerCaptured = true; 
                    break;
                }

                if (key.KeyChar == '\b' && captured.Count > 0)
                {
                    captured.RemoveAt(captured.Count - 1);
                }
                else
                {
                    captured.Add(key.KeyChar);
                }
            }
            Thread.Sleep(50);
        }
        string answer = new string(captured.ToArray());

        correct = questionCorrect.Invoke(answer);
        answered.Set();
    }
    catch (ThreadAbortException)
    {
        // will be thrown when the thread times out. 
    }
}
Community
  • 1
  • 1
  • I tried this and found the claim in your first sentence to be wrong. `Console.ReadLine` does not block other threads. – Timwi Jan 21 '11 at 02:34
  • @Timwi - It seems to be in this case. Edited my answer to allow switching between input methods. Please feel free to point out what I've done wrong. –  Jan 21 '11 at 03:25
  • @Will: I’ve tried some more and it seems that `thread.Abort()` simply does not abort the thread while it’s inside `Console.ReadLine()`. – Timwi Jan 21 '11 at 11:14
  • @Timwi - ok, I've updated my description. That said, I believe the rest of my answer was (and is) correct, yes? –  Jan 21 '11 at 12:43
  • @Will: I haven’t tried it, but on first inspection I suspect you still have the same problem because your thread would be stuck in `Console.ReadKey()`. Perhaps you should call that only if `Console.KeyAvailable` is true. (P.S. The documentation for `Thread.Abort` says this: *If Abort is called on a managed thread while it is executing unmanaged code, a ThreadAbortException is not thrown until the thread returns to managed code.*) – Timwi Jan 21 '11 at 13:15
0

Take a look at the example on MSDN's Thread.Join() page. The example uses two different methods for passing work to a thread. regularThread is what you are trying to do. For the simple task in your example, I think doing a join() without events or locks is a reasonable solution. If you are prototyping to make a product that is much more robust than your example implies, then you should also: 1) read up on the ThreadPool . It avoids the cost of creating/disposing threads. 2) Put a lock() block around reads and writes to the answer variable.

A word of warning: Other answers mention using Thread.Abort(). Calling Thread.Abort() on the currently executing thread is fine and nearly equivalent of throwing an exception. However calling Thread.Abort() on a different thread should be avoided. There's several scenarios where it can result in the thread not cleaning up properly.

ErnieL
  • 5,773
  • 1
  • 23
  • 27