【问题标题】:Changing thread context in C# console application在 C# 控制台应用程序中更改线程上下文
【发布时间】:2017-10-21 10:50:18
【问题描述】:

我有一个 C# 控制台应用程序,除其他外,我可以通过 TCP 套接字连接获取输入。通过socket的receive函数接收输入时如何切换到主线程?

与 WPF 中的类似:

public void TaskDispatcher()
{
    if (DispatcherObjectForTaskDispatcher.Thread != System.Threading.Thread.CurrentThread)
        DispatcherObjectForTaskDispatcher.Invoke(new TaskDispatcherDelegate(TaskDispatcher));
    else
    {
        // Do some thing in the UI thread
    }
}

【问题讨论】:

  • 不是很清楚。控制台应用程序没有 UI,因此没有“UI 线程”。它也没有 SyncContext。您可能只需要一个生产者/消费者设置或其他东西。
  • 不当然不是主线程,静态void Main(string[] args)函数运行的线程和receive函数有不同的线程。带有 UI 线程的只是一个例子。
  • 但是,生产者-消费者数据流模式似乎是正确的提示。
  • 裸线程无法“接收工作”。它必须先运行一个 SyncContext。
  • 在结构上区分控制台模式应用程序和 GUI 应用程序的唯一因素是 Main() 方法中的 Application.Run() 调用。你想要它。很容易解决,创建一个 Winforms 或 WPF 应用程序,在 Application 选项卡上将输出类型设置为“Console”,不要创建任何窗口和 presto chango,你已经得到了你需要的东西。

标签: c# multithreading console console-application


【解决方案1】:

只需使用Producer-Consumer 模式,如下面的工作示例所示。将来自其他线程的作业排入队列,让主线程处理来自作业队列的排队作业。

我使用了一个 timer 线程和一个 user input 线程来模拟 2 个线程来生成作业。您可以实现您的 TCP 事件以将作业排入作业队列中。您应该将任何相关对象作为参数存储在您的作业中,以供以后处理。您还必须定义一个由作业调用的函数,该函数将在主线程中运行。

这里使用的主线程仅用于使作业出队并处理它们,但如果您稍微改进此代码,您可以使用任何其他线程来实现此目的。

您甚至可以实现多线程处理,更多的处理线程从同一个作业队列中出列。请注意,这会带来新的并发问题,您可能需要处理这些问题。这是在您的应用程序中获得更多处理能力的缺点。有些场景适合多线程处理(例如视频/图像处理),有些则不适合。

下面的代码是在 Visual Studio 2017DotNET 4.6.1控制台应用程序项目中编写的完整工作示例。只需复制、粘贴,然后按 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}");
            }
        }

    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    相关资源
    最近更新 更多