一、概述
在平时我们的开发中,多线程也是经常用到的,尤其是我们做上位机行业的,平时更是必不可少,在以前我做 Unity3d 开发时,其实并不用关心线程的问题,在 Unity 的开发中,所有代码基本都是单线程运行,而且还可以保持比较高的运行速度,当然,这不是本次要讨论的话题。
有人可能会问我这么做的意义,系统自动分配线程不是更好么?当然好,只是有时候调用其他的一些框架,就避免不了需要锁定线程,比如,C# 调用 C++ 的 DLL,最近在做发那科的上位机程序,在调用 fanuc 的一些方法时,需要传入一个句柄,在测试中,我发现如果使用多线程,切换到了其他的线程根本无法行的通,返回数据都是报错,但是在主程序中,虽然可以正常运行,但是一但执行了写入G代码这类接口时,整个程序全部卡死,这就不得不用多线程,并且必须让指定的代码,在指定的程序中运行。
二、实现功能
新建一个 winform 项目,界面中就两个按钮,用来测试多线程的影响
新建一个类 MessagePump,当前类的功能就是开启一个线程,加入一个任务队列,并重复的检测在任务队列中有没有可以执行的任务。
其实当类还可以写的更复杂,比如,自定义一个任务系统,可以传入任务的名字,任务的委托,和回调的委托,任务需要的一些参数等,这里我就不具体去写啦,有需求的可以自己试试。
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;internal class MessagePump
{private static bool m_Working = false;private static Queue<Action> m_Actions = new Queue<Action>();public static void Start(){if (m_Working)return;m_Working = true;Thread t = new Thread(DoPump);t.Name = "Message Pump Thread";t.Start();}private static void DoPump(){while (m_Working){try{Monitor.Enter(m_Actions);while (m_Actions.Count > 0){Console.WriteLine("------start------");m_Actions.Dequeue()();Console.WriteLine("------end------");}}finally{Monitor.Exit(m_Actions);}Thread.Sleep(500);}}public static void Stop(){m_Working = false;}public static void AddMessage(Action act){Task.Run(() =>{lock (m_Actions){m_Actions.Enqueue(act);}});}
}
Form1 窗体的代码
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace 多线程
{public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){#region 注释//var c = new System.Collections.Concurrent.BlockingCollection<Tuple<bool, Action>>();//var t = new Thread(() =>//{// while (true)// {// var item = c.Take();// if (!item.Item1) break;// item.Item2();// }// Console.WriteLine("Exiting thread");//});//t.Start();//Console.WriteLine("Press any key to queue first action");//Console.ReadKey();//c.Add(Tuple.Create<bool, Action>(true, () => Console.WriteLine("Executing first action")));//Console.WriteLine("Press any key to queue second action");//Console.ReadKey();//c.Add(Tuple.Create<bool, Action>(true, () => Console.WriteLine("Executing second action")));//Console.WriteLine("Press any key to stop the thread");//Console.ReadKey();//c.Add(Tuple.Create<bool, Action>(false, null));//Console.WriteLine("Press any key to exit");#endregionMessagePump.Start();MessagePump.AddMessage(() =>{string threadid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 1, threadid);Thread.Sleep(1000);Console.WriteLine("-------------任务1完成-------------");});MessagePump.AddMessage(() =>{string threadid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 1, threadid);int value = 0;while (true){Thread.Sleep(1000);value++;if (value > 10){break;}}Console.WriteLine("-------------任务2完成-------------");});MessagePump.AddMessage(() =>{string threadid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 3, threadid);Thread.Sleep(3000);Console.WriteLine("-------------任务3完成-------------");});}private void Form1_FormClosing(object sender, FormClosingEventArgs e){MessagePump.Stop();}//使用异步打开定时器private void button1_Click(object sender, EventArgs e){isStop = true;DosomeThingAsync();}private static bool isStop = false;private static async void DosomeThingAsync(){while (isStop){await Task.Delay(TimeSpan.FromSeconds(2));string threadid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine("定时器--线程ID:" + threadid);}}//新添加一个任务private void button2_Click(object sender, EventArgs e){MessagePump.AddMessage(() =>{string threadid = Thread.CurrentThread.ManagedThreadId.ToString();Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 5, threadid);Thread.Sleep(5000);Console.WriteLine("-------------任务{0}完成-------------", 5);});}}
}
窗体在运行后,会自动添加任务,并执行,还任务还没执行完成时,我们点击 “使用异步打开定时器” 这个按钮,临时切换一些线程,看看之前添加的线程会不会改变线程ID
点击 “新添加一个任务” 按钮,可以看到,线程的ID并没有变,这样,我们锁定线程去执行代码的功能就实现了。
所有的代码都在这里了,源码我就不上传了。
三、SynchronizationContext
提供在各种同步模型中传播同步上下文的基本功能。
类 SynchronizationContext 是提供不同步的自由线程上下文的基类。
此类实现的同步模型的目的是允许公共语言运行时的内部异步/同步操作在不同的同步模型中正常运行。 此模型还简化了托管应用程序为了在不同的同步环境中正常工作而必须遵循的一些要求。
同步模型的提供程序可以扩展此类,并为这些方法提供自己的实现。
上面是微软的一些解释,推荐帖子:
同步上下文(SynchronizationContext) 和 C#中跨线程更新UI的方法总结_c# synchronizationcontext_kalvin_y_liu的博客-CSDN博客
c#:深入理解SynchronizationContext_c# synchronizationcontext_jackletter的博客-CSDN博客
SynchronizationContext类的方法原型如下:
namespace System.Threading
{//// 摘要:// 提供在各种同步模型中传播同步上下文的基本功能。public class SynchronizationContext{//// 摘要:// 创建 System.Threading.SynchronizationContext 类的新实例。public SynchronizationContext();//// 摘要:// 获取当前线程的同步上下文。//// 返回结果:// 一个 System.Threading.SynchronizationContext 对象,它表示当前同步上下文。public static SynchronizationContext Current { get; }//// 摘要:// 设置当前同步上下文。//// 参数:// syncContext:// 要设置的 System.Threading.SynchronizationContext 对象。[SecurityCritical][TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]public static void SetSynchronizationContext(SynchronizationContext syncContext);//// 摘要:// 用于等待指定数组中的任一元素或所有元素接收信号的 Helper 函数。//// 参数:// waitHandles:// 一个类型为 System.IntPtr 的数组,其中包含本机操作系统句柄。//// waitAll:// 若等待所有句柄,则为 true;若等待任一句柄,则为 false。//// millisecondsTimeout:// 等待的毫秒数,或为 System.Threading.Timeout.Infinite (-1),表示无限期等待。//// 返回结果:// 满足等待的对象的数组索引。[CLSCompliant(false)][PrePrepareMethod][ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)][SecurityCritical]protected static int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);//// 摘要:// 当在派生类中重写时,创建同步上下文的一个副本。//// 返回结果:// 一个新的 System.Threading.SynchronizationContext 对象。public virtual SynchronizationContext CreateCopy();//// 摘要:// 确定是否需要等待通知。//// 返回结果:// 如果需要等待通知,则为 true;否则为 false。public bool IsWaitNotificationRequired();//// 摘要:// 当在派生类中重写时,响应操作已完成的通知。public virtual void OperationCompleted();//// 摘要:// 当在派生类中重写时,响应操作已开始的通知。public virtual void OperationStarted();//// 摘要:// 当在派生类中重写时,将异步消息调度到一个同步上下文。//// 参数:// d:// 要调用的 System.Threading.SendOrPostCallback 委托。//// state:// 传递给委托的对象。public virtual void Post(SendOrPostCallback d, object state);//// 摘要:// 当在派生类中重写时,将一个同步消息调度到一个同步上下文。//// 参数:// d:// 要调用的 System.Threading.SendOrPostCallback 委托。//// state:// 传递给委托的对象。//// 异常:// T:System.NotSupportedException:// 在 Windows Store 应用程序中调用的方法。用于 Windows Store 应用程序的 System.Threading.SynchronizationContext// 的实现应用不支持 System.Threading.SynchronizationContext.Send(System.Threading.SendOrPostCallback,System.Object)// 方法。public virtual void Send(SendOrPostCallback d, object state);//// 摘要:// 等待指定数组中的任一元素或所有元素接收信号。//// 参数:// waitHandles:// 一个类型为 System.IntPtr 的数组,其中包含本机操作系统句柄。//// waitAll:// 若等待所有句柄,则为 true;若等待任一句柄,则为 false。//// millisecondsTimeout:// 等待的毫秒数,或为 System.Threading.Timeout.Infinite (-1),表示无限期等待。//// 返回结果:// 满足等待的对象的数组索引。//// 异常:// T:System.ArgumentNullException:// waitHandles 为 null。[CLSCompliant(false)][PrePrepareMethod][SecurityCritical]public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);//// 摘要:// 设置指示需要等待通知的通知,并准备回调方法以使其在发生等待时可以更可靠地被调用。[SecuritySafeCritical]protected void SetWaitNotificationRequired();}
}
使用 SynchronizationContext 也可以实现切换线程执行的效果,只是平时这种方式在我们工作中用的并不是很多,具体案例就不做介绍了。
如果当前的文章对你有所帮助,欢迎点赞 + 留言,有疑问也可以私信,谢谢。
end