4

I have a C# console app in which I can get, among other things, input via a TCP socket connection. How do I switch to the main thread when I receive an input via the receive function over the socket?

So similar to something like this in WPF:

public void TaskDispatcher()
{
    if (DispatcherObjectForTaskDispatcher.Thread != System.Threading.Thread.CurrentThread)
        DispatcherObjectForTaskDispatcher.Invoke(new TaskDispatcherDelegate(TaskDispatcher));
    else
    {
        // Do some thing in the UI thread
    }
}
uhwgmxorg
  • 53
  • 1
  • 6
  • 2
    Not very clear. A Console app has no UI and therefore no 'UI thread'. It also has no SyncContext. You probably just need a Producer/Consumer setup or eomething. – H H Oct 21 '17 at 11:14
  • No of course not but a main thread, the thread in which the static void Main(string[] args) function runs and the receive function has a different thread. The one with the UI thread was just one example. – uhwgmxorg Oct 21 '17 at 11:52
  • But yes the Producer-Consumer Dataflow Pattern seems to be the right hint. – uhwgmxorg Oct 21 '17 at 12:06
  • A bare thread cannot 'receive work'. It has to run a SyncContext first. – H H Oct 21 '17 at 12:09
  • 2
    The one-and-only thing that structurally distinguishes a console mode app from a GUI app is the Application.Run() call in the Main() method. You want it. So easy to solve, create a Winforms or WPF app, on the Application tab set the Output type to "Console", don't create any window and presto chango, you've got what you need. – Hans Passant Oct 21 '17 at 12:36

1 Answers1

2

Just use a Producer-Consumer pattern as in the working example below. Enqueue jobs from other threads and let the main thread process the queued jobs from a job queue.

I used a timer thread and a user input thread to simulate 2 threads producing jobs. You could implement your TCP events to just enqueue a job in the job queue. You should store any relevant objects as arguments inside your job, for later processing. You must also define a function to be called by the job, which will run in the main thread.

The main thread is used here just for dequeueing jobs and processing them, but you could use any other thread for this purpose if you improve this code a little bit.

You could even implement multi-threading processing, on which more processing threads dequeue from the same job queue. Be aware this brings new concurrency problems which you may have to deal with. That's the drawback for gaining much more processing power in your application. Some scenarios are suitable for multi-threading processing (e.g. video / image processing) while some others are not.

The code below is a full working example written in a Visual Studio 2017, DotNET 4.6.1, console application project. Just copy, paste, and hit F5.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;

// Compiled and tested in: Visual Studio 2017, DotNET 4.6.1

namespace MyNamespace
{
    public class Program
    {
        public static void Main(string[] args)
        {
            MyApplication app = new MyApplication();
            app.Run();
        }
    }

    public class MyApplication
    {
        private BlockingCollection<Job> JobQueue = new BlockingCollection<Job>();
        private CancellationTokenSource JobCancellationTokenSource = new CancellationTokenSource();
        private CancellationToken JobCancellationToken;
        private Timer Timer;
        private Thread UserInputThread;



        public void Run()
        {
            // Give a name to the main thread:
            Thread.CurrentThread.Name = "Main";

            // Fires a Timer thread:
            Timer = new Timer(new TimerCallback(TimerCallback), null, 1000, 2000);

            // Fires a thread to read user inputs:
            UserInputThread = new Thread(new ThreadStart(ReadUserInputs))
            {
                Name = "UserInputs",
                IsBackground = true
            };
            UserInputThread.Start();

            // Prepares a token to cancel the job queue:
            JobCancellationToken = JobCancellationTokenSource.Token;

            // Start processing jobs:
            ProcessJobs();

            // Clean up:
            JobQueue.Dispose();
            Timer.Dispose();
            UserInputThread.Abort();

            Console.WriteLine("Done.");
        }



        private void ProcessJobs()
        {
            try
            {
                // Checks if the blocking collection is still up for dequeueing:
                while (!JobQueue.IsCompleted)
                {
                    // The following line blocks the thread until a job is available or throws an exception in case the token is cancelled:
                    JobQueue.Take(JobCancellationToken).Run();
                }
            }
            catch { }
        }



        private void ReadUserInputs()
        {
            // User input thread is running here.
            ConsoleKey key = ConsoleKey.Enter;

            // Reads user inputs and queue them for processing until the escape key is pressed:
            while ((key = Console.ReadKey(true).Key) != ConsoleKey.Escape)
            {
                Job userInputJob = new Job("UserInput", this, new Action<ConsoleKey>(ProcessUserInputs), key);
                JobQueue.Add(userInputJob);
            }
            // Stops processing the JobQueue:
            JobCancellationTokenSource.Cancel();
        }

        private void ProcessUserInputs(ConsoleKey key)
        {
            // Main thread is running here.
            Console.WriteLine($"You just typed '{key}'. (Thread: {Thread.CurrentThread.Name})");
        }



        private void TimerCallback(object param)
        {
            // Timer thread is running here.
            Job job = new Job("TimerJob", this, new Action<string>(ProcessTimer), "A job from timer callback was processed.");
            JobQueue.TryAdd(job); // Just enqueues the job for later processing
        }

        private void ProcessTimer(string message)
        {
            // Main thread is running here.
            Console.WriteLine($"{message} (Thread: {Thread.CurrentThread.Name})");
        }
    }



    /// <summary>
    /// The Job class wraps an object's method call, with or without arguments. This method is called later, during the Job execution.
    /// </summary>
    public class Job
    {
        public string Name { get; }
        private object TargetObject;
        private Delegate TargetMethod;
        private object[] Arguments;

        public Job(string name, object obj, Delegate method, params object[] args)
        {
            Name = name;
            TargetObject = obj;
            TargetMethod = method;
            Arguments = args;
        }

        public void Run()
        {
            try
            {
                TargetMethod.Method.Invoke(TargetObject, Arguments);
            }
            catch(Exception ex)
            {
                Debug.WriteLine($"Unexpected error running job '{Name}': {ex}");
            }
        }

    }
}
sɐunıɔןɐqɐp
  • 3,332
  • 15
  • 36
  • 40