Edit. My original answer from several years ago relied on the user typing in a magic string to end the console listener, and overlooked that CTL+C is already the default cancellation signal.
class Program
{
readonly static CancellationTokenSource _cancelTokenSrc = new CancellationTokenSource();
static void Main(string[] args)
{
// CTL + C is the built-in cancellation for console apps;
Console.CancelKeyPress += Console_CancelKeyPress;
CancellationToken cancelToken = _cancelTokenSrc.Token;
Console.WriteLine("Type commands followed by 'ENTER'");
Console.WriteLine("Press CTL+C to Terminate");
Console.WriteLine();
try
{
// thread that performs background work until cancelled
Task.Run(() => DoWork(), cancelToken);
// thread that listens for keyboard input until cancelled
Task.Run(() => ListenForInput(), cancelToken);
// continue listening until cancel signal is sent
cancelToken.WaitHandle.WaitOne();
cancelToken.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation Canceled.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
static void ListenForInput()
{
while (true)
{
string userInput = Console.ReadLine();
if(!String.IsNullOrWhiteSpace(userInput))
Console.WriteLine($"Executing user command {userInput}...");
}
}
static void DoWork()
{
while (true)
{
Thread.Sleep(3000);
Console.WriteLine("Doing work...");
}
}
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
// we want to cancel the default behavior so we can send the cancellation signal
// to our background threads and not just terminate here
e.Cancel = true;
Console.WriteLine("Cancelling...");
_cancelTokenSrc.Cancel();
}
}