在C#控制台应用程序中更改线程上下文 [英] Changing thread context in C# console application

查看:69
本文介绍了在C#控制台应用程序中更改线程上下文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



与WPF中的类似内容类似:

  public void TaskDispatcher()
{
if(DispatcherObjectForTaskDispatcher.Thread!= System.Threading.Thread.CurrentThread)
DispatcherObjectForTaskDispatcher.Invoke(新TaskDispatcherDelegate(TaskDispatcher));
else
{
//在UI线程
}
}

解决方案

只需使用生产者/消费者模式,如下面的工作示例所示。将其他线程中的作业排队,然后让主线程处理作业队列中排队的作业。



我使用了 timer 线程和用户输入线程来模拟2个线程生成作业。您可以实现TCP事件以仅将作业加入作业队列。您应将任何相关对象存储为作业内部的参数,以备后用。您还必须定义作业要调用的函数,该函数将在主线程中运行。



此处,主线程仅用于使作业出队并对其进行处理,但是如果您对此代码进行一点改进,就可以使用其他任何线程。



您甚至可以实现多线程处理,在此之上,更多的处理线程将从相同的作业队列。请注意,这会带来新的并发问题,您可能需要解决。这就是在应用程序中获得更多处理能力的缺点。有些情况适用于多线程处理(例如,视频/图像处理),而有些则不适合。



下面的代码是用 Visual Studio 2017 DotNET 4.6.1 控制台应用程序项目。只需复制,粘贴并按F5键即可。

 使用系统; 
使用System.Collections.Concurrent;
使用System.Diagnostics;
使用System.Threading;

//在以下环境中进行编译和测试:Visual Studio 2017,DotNET 4.6.1

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

公共类MyApplication
{
private BlockingCollection< Job> JobQueue = new BlockingCollection< Job>();
private CancellationTokenSource JobCancellationTokenSource = new CancellationTokenSource();
私人CancellationToken JobCancellationToken;
私人计时器计时器;
私有线程UserInputThread;



public void Run()
{
//为主线程命名:
Thread.CurrentThread.Name = 主要;

//触发计时器线程:
Timer = new Timer(new TimerCallback(TimerCallback),null,1000,2000);

//触发线程读取用户输入:
UserInputThread = new Thread(new ThreadStart(ReadUserInputs))
{
Name = UserInputs,
IsBackground = true
};
UserInputThread.Start();

//准备一个令牌来取消作业队列:
JobCancellationToken = JobCancellationTokenSource.Token;

//开始处理作业:
ProcessJobs();

//清理:
JobQueue.Dispose();
Timer.Dispose();
UserInputThread.Abort();

Console.WriteLine( Done。);
}



私人无效ProcessJobs()
{
try
{
// //检查是否阻塞的收集仍然可以出队:
while(!JobQueue.IsCompleted)
{
//下面的行阻塞线程,直到有可用的作业或抛出异常(如果令牌为已取消:
JobQueue.Take(JobCancellationToken).Run();
}
}
catch {}
}



private void ReadUserInputs()
{
//用户输入线程正在此处运行。
ConsoleKey键= ConsoleKey.Enter;

//读取用户输入并将它们排队等待处理,直到按下逃逸键为止:
while((key = Console.ReadKey(true).Key)!= ConsoleKey.Escape)
{
Job userInputJob = new Job( UserInput,this,new Action< ConsoleKey>(ProcessUserInputs),key);
JobQueue.Add(userInputJob);
}
//停止处理JobQueue:
JobCancellationTokenSource.Cancel();
}

private void ProcessUserInputs(ConsoleKey key)
{
//主线程在此处运行。
Console.WriteLine($您刚刚输入了 {key}。(线程:{Thread.CurrentThread.Name}));
}



private void TimerCallback(object param)
{
//计时器线程在此处运行。
Job job = new Job( TimerJob,this,new Action< string>(ProcessTimer),处理了来自计时器回调的作业。);
JobQueue.TryAdd(job); //只是将作业排队,以便以后处理
}

private void ProcessTimer(string message)
{
//主线程在此处运行。
Console.WriteLine($ {message}(Thread:{Thread.CurrentThread.Name}));
}
}



///< summary>
/// Job类包装对象的方法调用,带或不带参数。稍后在Job执行期间调用此方法。
///< / summary>
公共类Job
{
public string名称{get; }
私有对象TargetObject;
私人代表TargetMethod;
private object []参数;

公共作业(字符串名称,对象obj,委托方法,参数对象[]参数)
{
Name = name;
TargetObject = obj;
TargetMethod =方法;
参数= args;
}

public void Run()
{
try
{
TargetMethod.Method.Invoke(TargetObject,Arguments);
}
catch(异常例外)
{
Debug.WriteLine($正在运行作业'{Name}'的意外错误:{ex});
}
}

}
}


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
    }
}

解决方案

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}");
            }
        }

    }
}

这篇关于在C#控制台应用程序中更改线程上下文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆