• C# 让程序代码在固定的线程里运行


    一、概述

    在平时我们的开发中,多线程也是用的非常多的,尤其是做上位机行业的,平时更是必不可少,在以前我从事 Unity3d 开发时,并不用关心线程的问题,在 Unity 中的脚本代码基本都是单线程运行(协程除外),而且还可以保持比较高的运行速度,当然,这不是本次要讨论的话题。

    有人可能会问我这么做的意义,系统自动分配线程不是更好么?当然好,只是有时候调用其他的一些框架,就避免不了需要锁定线程。最近在做发那科的上位机程序,在调用 fanuc 的一些方法时,需要传入一个句柄,在测试中我发现如果使用多线程,切换到了其他的线程执行函数,返回结果都是错误的,虽然可以将代码全部写在UI线程中去运行,但调用 fanuc 的某些接口时,很容易将整个程序全部卡死,成为无响应模式,这就不得不使用多线程,并且必须让指定的代码,在指定的线程中运行。

    二、实现功能

    新建一个 winform 项目,界面中就两个按钮,用来测试多线程的影响

    新建一个类 MessagePump,当前类的功能就是开启一个线程,加入一个任务队列,并重复的检测在任务队列中有没有可以执行的任务。

    其实当类还可以写的更复杂,比如,自定义一个任务系统,可以传入任务的名字,任务的委托,和回调的委托,任务需要的一些参数等,这里我就不具体去写啦,有需求的可以自己试试。

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Threading;
    4. using System.Threading.Tasks;
    5. internal class MessagePump
    6. {
    7. private static bool m_Working = false;
    8. private static Queue m_Actions = new Queue();
    9. public static void Start()
    10. {
    11. if (m_Working)
    12. return;
    13. m_Working = true;
    14. Thread t = new Thread(DoPump);
    15. t.Name = "Message Pump Thread";
    16. t.Start();
    17. }
    18. private static void DoPump()
    19. {
    20. while (m_Working)
    21. {
    22. try
    23. {
    24. Monitor.Enter(m_Actions);
    25. while (m_Actions.Count > 0)
    26. {
    27. Console.WriteLine("------start------");
    28. m_Actions.Dequeue()();
    29. Console.WriteLine("------end------");
    30. }
    31. }
    32. finally
    33. {
    34. Monitor.Exit(m_Actions);
    35. }
    36. Thread.Sleep(500);
    37. }
    38. }
    39. public static void Stop()
    40. {
    41. m_Working = false;
    42. }
    43. public static void AddMessage(Action act)
    44. {
    45. Task.Run(() =>
    46. {
    47. lock (m_Actions)
    48. {
    49. m_Actions.Enqueue(act);
    50. }
    51. });
    52. }
    53. }

    Form1 窗体的代码

    1. using System;
    2. using System.Threading;
    3. using System.Threading.Tasks;
    4. using System.Windows.Forms;
    5. namespace 多线程
    6. {
    7. public partial class Form1 : Form
    8. {
    9. public Form1()
    10. {
    11. InitializeComponent();
    12. }
    13. private void Form1_Load(object sender, EventArgs e)
    14. {
    15. #region 注释
    16. //var c = new System.Collections.Concurrent.BlockingCollection>();
    17. //var t = new Thread(() =>
    18. //{
    19. // while (true)
    20. // {
    21. // var item = c.Take();
    22. // if (!item.Item1) break;
    23. // item.Item2();
    24. // }
    25. // Console.WriteLine("Exiting thread");
    26. //});
    27. //t.Start();
    28. //Console.WriteLine("Press any key to queue first action");
    29. //Console.ReadKey();
    30. //c.Add(Tuple.Create(true, () => Console.WriteLine("Executing first action")));
    31. //Console.WriteLine("Press any key to queue second action");
    32. //Console.ReadKey();
    33. //c.Add(Tuple.Create(true, () => Console.WriteLine("Executing second action")));
    34. //Console.WriteLine("Press any key to stop the thread");
    35. //Console.ReadKey();
    36. //c.Add(Tuple.Create(false, null));
    37. //Console.WriteLine("Press any key to exit");
    38. #endregion
    39. MessagePump.Start();
    40. MessagePump.AddMessage(() =>
    41. {
    42. string threadid = Thread.CurrentThread.ManagedThreadId.ToString();
    43. Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 1, threadid);
    44. Thread.Sleep(1000);
    45. Console.WriteLine("-------------任务1完成-------------");
    46. });
    47. MessagePump.AddMessage(() =>
    48. {
    49. string threadid = Thread.CurrentThread.ManagedThreadId.ToString();
    50. Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 1, threadid);
    51. int value = 0;
    52. while (true)
    53. {
    54. Thread.Sleep(1000);
    55. value++;
    56. if (value > 10)
    57. {
    58. break;
    59. }
    60. }
    61. Console.WriteLine("-------------任务2完成-------------");
    62. });
    63. MessagePump.AddMessage(() =>
    64. {
    65. string threadid = Thread.CurrentThread.ManagedThreadId.ToString();
    66. Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 3, threadid);
    67. Thread.Sleep(3000);
    68. Console.WriteLine("-------------任务3完成-------------");
    69. });
    70. }
    71. private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    72. {
    73. MessagePump.Stop();
    74. }
    75. //使用异步打开定时器
    76. private void button1_Click(object sender, EventArgs e)
    77. {
    78. isStop = true;
    79. DosomeThingAsync();
    80. }
    81. private static bool isStop = false;
    82. private static async void DosomeThingAsync()
    83. {
    84. while (isStop)
    85. {
    86. await Task.Delay(TimeSpan.FromSeconds(2));
    87. string threadid = Thread.CurrentThread.ManagedThreadId.ToString();
    88. Console.WriteLine("定时器--线程ID:" + threadid);
    89. }
    90. }
    91. //新添加一个任务
    92. private void button2_Click(object sender, EventArgs e)
    93. {
    94. MessagePump.AddMessage(() =>
    95. {
    96. string threadid = Thread.CurrentThread.ManagedThreadId.ToString();
    97. Console.WriteLine("-------------任务{0}当前的线程ID:{1}", 5, threadid);
    98. Thread.Sleep(5000);
    99. Console.WriteLine("-------------任务{0}完成-------------", 5);
    100. });
    101. }
    102. }
    103. }

    窗体在运行后,会自动添加任务,并执行,还任务还没执行完成时,我们点击 “使用异步打开定时器” 这个按钮,临时切换一些线程,看看之前添加的线程会不会改变线程ID

    点击 “新添加一个任务” 按钮,可以看到,线程的ID并没有变,这样,我们锁定线程去执行代码的功能就实现了。

    所有的代码都在这里了,源码我就不上传了。 

    三、SynchronizationContext

    提供在各种同步模型中传播同步上下文的基本功能。

    类 SynchronizationContext 是提供不同步的自由线程上下文的基类。

    此类实现的同步模型的目的是允许公共语言运行时的内部异步/同步操作在不同的同步模型中正常运行。 此模型还简化了托管应用程序为了在不同的同步环境中正常工作而必须遵循的一些要求。

    同步模型的提供程序可以扩展此类,并为这些方法提供自己的实现。

    上面是微软的一些解释,推荐帖子:

    同步上下文(SynchronizationContext) 和 C#中跨线程更新UI的方法总结_c# synchronizationcontext_kalvin_y_liu的博客-CSDN博客

    c#:深入理解SynchronizationContext_c# synchronizationcontext_jackletter的博客-CSDN博客

    SynchronizationContext类的方法原型如下:

    1. namespace System.Threading
    2. {
    3. //
    4. // 摘要:
    5. // 提供在各种同步模型中传播同步上下文的基本功能。
    6. public class SynchronizationContext
    7. {
    8. //
    9. // 摘要:
    10. // 创建 System.Threading.SynchronizationContext 类的新实例。
    11. public SynchronizationContext();
    12. //
    13. // 摘要:
    14. // 获取当前线程的同步上下文。
    15. //
    16. // 返回结果:
    17. // 一个 System.Threading.SynchronizationContext 对象,它表示当前同步上下文。
    18. public static SynchronizationContext Current { get; }
    19. //
    20. // 摘要:
    21. // 设置当前同步上下文。
    22. //
    23. // 参数:
    24. // syncContext:
    25. // 要设置的 System.Threading.SynchronizationContext 对象。
    26. [SecurityCritical]
    27. [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
    28. public static void SetSynchronizationContext(SynchronizationContext syncContext);
    29. //
    30. // 摘要:
    31. // 用于等待指定数组中的任一元素或所有元素接收信号的 Helper 函数。
    32. //
    33. // 参数:
    34. // waitHandles:
    35. // 一个类型为 System.IntPtr 的数组,其中包含本机操作系统句柄。
    36. //
    37. // waitAll:
    38. // 若等待所有句柄,则为 true;若等待任一句柄,则为 false。
    39. //
    40. // millisecondsTimeout:
    41. // 等待的毫秒数,或为 System.Threading.Timeout.Infinite (-1),表示无限期等待。
    42. //
    43. // 返回结果:
    44. // 满足等待的对象的数组索引。
    45. [CLSCompliant(false)]
    46. [PrePrepareMethod]
    47. [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    48. [SecurityCritical]
    49. protected static int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
    50. //
    51. // 摘要:
    52. // 当在派生类中重写时,创建同步上下文的一个副本。
    53. //
    54. // 返回结果:
    55. // 一个新的 System.Threading.SynchronizationContext 对象。
    56. public virtual SynchronizationContext CreateCopy();
    57. //
    58. // 摘要:
    59. // 确定是否需要等待通知。
    60. //
    61. // 返回结果:
    62. // 如果需要等待通知,则为 true;否则为 false。
    63. public bool IsWaitNotificationRequired();
    64. //
    65. // 摘要:
    66. // 当在派生类中重写时,响应操作已完成的通知。
    67. public virtual void OperationCompleted();
    68. //
    69. // 摘要:
    70. // 当在派生类中重写时,响应操作已开始的通知。
    71. public virtual void OperationStarted();
    72. //
    73. // 摘要:
    74. // 当在派生类中重写时,将异步消息调度到一个同步上下文。
    75. //
    76. // 参数:
    77. // d:
    78. // 要调用的 System.Threading.SendOrPostCallback 委托。
    79. //
    80. // state:
    81. // 传递给委托的对象。
    82. public virtual void Post(SendOrPostCallback d, object state);
    83. //
    84. // 摘要:
    85. // 当在派生类中重写时,将一个同步消息调度到一个同步上下文。
    86. //
    87. // 参数:
    88. // d:
    89. // 要调用的 System.Threading.SendOrPostCallback 委托。
    90. //
    91. // state:
    92. // 传递给委托的对象。
    93. //
    94. // 异常:
    95. // T:System.NotSupportedException:
    96. // 在 Windows Store 应用程序中调用的方法。用于 Windows Store 应用程序的 System.Threading.SynchronizationContext
    97. // 的实现应用不支持 System.Threading.SynchronizationContext.Send(System.Threading.SendOrPostCallback,System.Object)
    98. // 方法。
    99. public virtual void Send(SendOrPostCallback d, object state);
    100. //
    101. // 摘要:
    102. // 等待指定数组中的任一元素或所有元素接收信号。
    103. //
    104. // 参数:
    105. // waitHandles:
    106. // 一个类型为 System.IntPtr 的数组,其中包含本机操作系统句柄。
    107. //
    108. // waitAll:
    109. // 若等待所有句柄,则为 true;若等待任一句柄,则为 false。
    110. //
    111. // millisecondsTimeout:
    112. // 等待的毫秒数,或为 System.Threading.Timeout.Infinite (-1),表示无限期等待。
    113. //
    114. // 返回结果:
    115. // 满足等待的对象的数组索引。
    116. //
    117. // 异常:
    118. // T:System.ArgumentNullException:
    119. // waitHandles 为 null。
    120. [CLSCompliant(false)]
    121. [PrePrepareMethod]
    122. [SecurityCritical]
    123. public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
    124. //
    125. // 摘要:
    126. // 设置指示需要等待通知的通知,并准备回调方法以使其在发生等待时可以更可靠地被调用。
    127. [SecuritySafeCritical]
    128. protected void SetWaitNotificationRequired();
    129. }
    130. }

    使用 SynchronizationContext 也可以实现切换线程执行的效果,只是平时这种方式在我们工作中用的并不是很多,具体案例就不做介绍了。

    如果当前的文章对你有所帮助,欢迎点赞 + 留言,有疑问也可以私信,谢谢。

    end

  • 相关阅读:
    pytho你-opencv划痕检测
    (附源码)spring boot高校社团管理系统 毕业设计 231128
    JVS轻应用的组成与配置
    接口的安全设计要素:ticket,签名,时间戳
    VulnHub 兰皮昂 1 Lampiao
    推荐这3款图片流动特效神器,一键即可让照片“动”起来
    【CSS】CSS入门笔记第三弹~
    SourceTree在不使用SSH密钥的情况下连接远程仓库的方法(可进行远端拉取和推送)
    阿里一面:TCP 的 Keepalive 和 HTTP 的 Keep-Alive 是一个东西吗?
    【C语言刷题】牛客JZ65——不用四则运算作加法
  • 原文地址:https://blog.csdn.net/qq_38693757/article/details/131143560