User567845
User567845

Reputation: 15

Multithreaded console cursor manipulation race condition in C#

I would be very thankful if anyone could give me a pointer in the right direction towards solving this problem I've been having in the last two days: I am working on a typing software for the Visual Studio 2010 console and need to represent time and be able to get input from the keyboard at the same time.

The problem is that the ReadLine() method allows me to write only when the timer thread is sleeping, maybe I could solve it via delegates or advanced task continuation methods, I'm a bit confused really since these are new concepts. Here is the code (sorry for the ugly formatting):

using System;
using System.Threading.Tasks;
using System.Threading;


namespace Typing_Game_tests
{

    class Input_and_timer_multithread
    {
        public static void TimerThread() 
        { 
            for (int i = 1, mins = -1; i <= 1860; i++) 
            {
                Console.SetCursorPosition(0, 0);
                if (i % 60 == 1) 
                { 
                    mins++; 
                }

                Console.WriteLine("Timer: " + mins +  " minute(s) and " + i % 60 + " seconds elapsed");            

                Console.SetCursorPosition(0, 6); 
                Thread.Sleep(1000);
            }
        }


        static string keysRead;

        public static void GetInput()
        {
            while (Console.ReadKey(true).Key != ConsoleKey.Enter)
            {
                Console.SetCursorPosition(0, 6);
                keysRead = Console.ReadLine();
                Console.SetCursorPosition(0, 6);
            }

            Console.SetCursorPosition(0, 6);
            Console.WriteLine(keysRead);

        }

        static void Main(string[] args)
        {               
            Thread timerMain = new Thread(new ThreadStart(TimerThread));
            timerMain.Start();
            Task getinputMain = new Task(GetInput);
            getinputMain.Start();
        }
     }
  }

Most of the examples I have found and studied have lambda expressions and delegates, which are new subjects I still have some difficulties understanding and thus implementing. It would've been easier if I could've stopped the main timer thread until the ReadLine() did his job, but it wouldn't have made sense; plus I've tried using the Timers namespace, but I've had the same problem.

I wanted to give the impression that the cursor was permanently getting input from the user, but I still haven't found a way to efficiently switch back and forth the threads without deadlocking them. Thanks.

Upvotes: 0

Views: 618

Answers (1)

Hans Passant
Hans Passant

Reputation: 941455

This kind of code doesn't work any more since .NET 4.5. Console.ReadKey() and ReadLine() take a lock that prevents other threads from writing to the console. You'll need to replace it so the lock can't be taken anymore. That requires polling the keyboard with Console.KeyAvailable. Here is a simplistic replacement method for Console.ReadLine():

static object ConsoleLock = new object();

static string ReadString() {
    var buf = new StringBuilder();
    for (; ; ) {
        while (!Console.KeyAvailable) System.Threading.Thread.Sleep(31);
        lock(ConsoleLock) {
            var key = Console.ReadKey(true);
            if (key.Key == ConsoleKey.Enter) {
                Console.WriteLine();
                return buf.ToString();
            }
            else if (key.Key == ConsoleKey.Backspace) {
                if (buf.Length > 0) {
                    buf.Remove(buf.Length - 1, 1);
                    Console.Write("\b \b");
                }
            }
            else {
                buf.Append(key.KeyChar);
                Console.Write(key.KeyChar);
            }
        }
    }
}

Also lock on the ConsoleLock in your other thread that moves the cursor so output is strictly separated.

Upvotes: 2

Related Questions