• C#线程的参数传递、获取线程返回值以及处理多线程冲突


      C#作为一门优秀的开发语言,现在国内的流行度貌似不如以前,大家都不在意它的无所不能了。

      C#的灵活与强大只有在经常使用中才会有所领悟,适当地掌握它还是有必要的。

      在这里总结一下线程的传递参数以及获取线程的返回值,还有处理多线程之间可能引发的冲突以及解决办法。

      在C#中,开启一个线程很容易。

    1. Thread Th1= new Thread(func);
    2. Th1.Start();
    3. private void func(object Obj)
    4. {
    5. //处理代码
    6. }

      很多情况下,我们是需要对线程进行传递参数的,这个也简单。

      1、线程的单一参数传递

    1. private void button1_Click(object sender, EventArgs e)
    2. {
    3. Thread Th1= new Thread(func);
    4. Th1.Start("CSDN");
    5. Thread.Sleep(500);
    6. }
    7. private void func(object Obj)
    8. {
    9. string Str = Obj as string;
    10. textBox1.BeginInvoke(new Action(() =>
    11. {
    12. textBox1.Text = $"传入的参数:{Str}";
    13. }));
    14. }

      2、线程的多参数传递以及返回值

      上面的例子是单一的参数,参数要求是对象,使用的时候进行了拆箱,根据上面的例子对于多参数,可以使用中间对象来处理,就是在中间对象中放置参数和获取处理后的结果。

    1. private void button1_Click(object sender, EventArgs e)
    2. {
    3. FinancialInfo FI=new FinancialInfo();
    4. FI.PersonName = "CSDN";
    5. FI.PersonDeposit = 123;
    6. Thread Th1 = new Thread(FI.ThreadChangeFinanceialInfo);
    7. Th1.Start();
    8. Thread.Sleep(500);
    9. textBox1.Text=FI.PersonName+Environment.NewLine+FI.PersonDeposit.ToString();
    10. }
    11. private class FinancialInfo
    12. {
    13. private string Name=string.Empty;
    14. private int Deposit=0;
    15. public string PersonName
    16. {
    17. get { return Name; }
    18. set { Name = value; }
    19. }
    20. public int PersonDeposit
    21. {
    22. get { return Deposit; }
    23. set { Deposit = value; }
    24. }
    25. public void ThreadChangeFinanceialInfo()
    26. {
    27. this.Name = this.Name + " | C#";
    28. this.Deposit = this.Deposit + 100;
    29. }
    30. }

      3、使用Task.Result获取任务返回值

      上面的方法看似比较繁琐,使用Task则比较简单。

    1. FinancialInfo fi=new FinancialInfo();
    2. fi.PersonName = "CSDN";
    3. fi.PersonDeposit = 123;
    4. var task1 = Task.Factory.StartNew( fi.ThreadChangeFinanceialInfoResult );
    5. fi = task1.Result;
    6. textBox1.Text = textBox1.Text + fi.PersonName +"====="+fi.PersonDeposit.ToString()+Environment.NewLine;

      FinancialInfo定义与上面一样。

    1. private class FinancialInfo
    2. {
    3. private string Name=string.Empty;
    4. private int Deposit=0;
    5. public string PersonName
    6. {
    7. get { return Name; }
    8. set { Name = value; }
    9. }
    10. public int PersonDeposit
    11. {
    12. get { return Deposit; }
    13. set { Deposit = value; }
    14. }
    15. public void ThreadChangeFinanceialInfo()
    16. {
    17. this.Name = this.Name + " | C#";
    18. this.Deposit = this.Deposit + 100;
    19. }
    20. public FinancialInfo ThreadChangeFinanceialInfoResult()
    21. {
    22. FinancialInfo fi=new FinancialInfo();
    23. fi.Name = this.Name + " | C#";
    24. fi.Deposit = this.Deposit + 100;
    25. return fi;
    26. }
    27. }

      上面的写法就好看多了。

      4、多线程可能引起的冲突以及解决办法

      多线程在处理同一对象时容易引起潜在的冲突,这个显而易见,例如:

    1. private void button1_Click(object sender, EventArgs e)
    2. {
    3. FinancialInfo FI = new FinancialInfo();
    4. FI.PersonName = "CSDN";
    5. FI.PersonDeposit = 123;
    6. Thread Th1 = new Thread(FI.ThreadAdd);
    7. Thread Th2 = new Thread(FI.ThreadReduce);
    8. Th1.Start();
    9. Th2.Start();
    10. Thread.Sleep(5000);
    11. textBox1.Text = textBox1.Text + FI.PersonName +"|"+FI.PersonDeposit.ToString()+Environment.NewLine;
    12. }
    13. private class FinancialInfo
    14. {
    15. private string Name=string.Empty;
    16. private int Deposit=0;
    17. public string PersonName
    18. {
    19. get { return Name; }
    20. set { Name = value; }
    21. }
    22. public int PersonDeposit
    23. {
    24. get { return Deposit; }
    25. set { Deposit = value; }
    26. }
    27. public void ThreadAdd()
    28. {
    29. for (int i = 0; i < 1000000; i++)
    30. {
    31. this.Deposit = this.Deposit + 1;
    32. }
    33. }
    34. public void ThreadReduce()
    35. {
    36. for (int i = 0; i < 1000000; i++)
    37. {
    38. this.Deposit = this.Deposit - 1;
    39. }
    40. }
    41. }

      显示结果:

      按道理, FI.PersonDeposit的值是123,加了1000000,也减了1000000,那么最终的结果应该还是123,为什么会是这样呢?

      这就是多线程在处理同一对象时所产生的冲突了,产生的就是所谓的“脏数据”。

      上面的代码因为等待线程执行完,进行了休眠,可以使用Task来写更简单。

    1. var task1 = new Task(FI.ThreadAdd);
    2. var task2 = new Task(FI.ThreadReduce);
    3. task1.Start();
    4. task2.Start();
    5. Task.WaitAll(task1,task2);

      Task是比Thread更加高级的概念,一个Task至少包含一个Thread。

      解决上面的冲突就是对可能引起冲突的对象进行加锁判断。

      完整代码:

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Drawing.Text;
    7. using System.Linq;
    8. using System.Text;
    9. using System.Threading.Tasks;
    10. using System.Windows.Forms;
    11. namespace MultiThread
    12. {
    13. public partial class Form3 : Form
    14. {
    15. private static readonly object LockObj=new object();
    16. public Form3()
    17. {
    18. InitializeComponent();
    19. }
    20. private void button1_Click(object sender, EventArgs e)
    21. {
    22. FinancialInfo FI = new FinancialInfo();
    23. FI.PersonName = "CSDN";
    24. FI.PersonDeposit = 123;
    25. var task1 = new Task(FI.ThreadAdd);
    26. var task2 = new Task(FI.ThreadReduce);
    27. task1.Start();
    28. task2.Start();
    29. Task.WaitAll(task1, task2);
    30. textBox1.Text = textBox1.Text + FI.PersonName +"|"+FI.PersonDeposit.ToString()+Environment.NewLine;
    31. }
    32. private class FinancialInfo
    33. {
    34. private string Name=string.Empty;
    35. private int Deposit=0;
    36. public string PersonName
    37. {
    38. get { return Name; }
    39. set { Name = value; }
    40. }
    41. public int PersonDeposit
    42. {
    43. get { return Deposit; }
    44. set { Deposit = value; }
    45. }
    46. public void ThreadChangeFinanceialInfo()
    47. {
    48. this.Name = this.Name + " | C#";
    49. this.Deposit = this.Deposit + 100;
    50. }
    51. public void ThreadAdd()
    52. {
    53. for (int i = 0; i < 1000000; i++)
    54. {
    55. lock(LockObj)
    56. {
    57. this.Deposit = this.Deposit + 1;
    58. }
    59. }
    60. }
    61. public void ThreadReduce()
    62. {
    63. for (int i = 0; i < 1000000; i++)
    64. {
    65. lock(LockObj)
    66. {
    67. this.Deposit = this.Deposit - 1;
    68. }
    69. }
    70. }
    71. }
    72. }
    73. }

      显示结果:

      上面显示出了正确的结果,但是会耗时。

      5、多线程之间的协调

      在多线程需要独占访问资源的时候,AutoResetEvent 允许线程通过发信号互相通信。
      通过 WaitOne 来等待信号,调用 Set 发出资源可用的信号。
      这个使用比较简单。

      举例让两个线程交替执行。

      下面是完整的代码。

    1. using System;
    2. using System.Threading;
    3. namespace MultiThread2023
    4. {
    5. public partial class Form1 : Form
    6. {
    7. static AutoResetEvent event1 = new AutoResetEvent(false);
    8. static AutoResetEvent event2 = new AutoResetEvent(false);
    9. public Form1()
    10. {
    11. InitializeComponent();
    12. Control.CheckForIllegalCrossThreadCalls = false;
    13. }
    14. private void button1_Click(object sender, EventArgs e)
    15. {
    16. textBox1.Text += "主线程启动"+Environment.NewLine;
    17. Thread t1 = new Thread(MyTask1);
    18. Thread t2 = new Thread(MyTask2);
    19. t1.Start();
    20. t2.Start();
    21. textBox1.Text += "线程1执行" + Environment.NewLine;
    22. event1.Set();
    23. }
    24. private void MyTask1()
    25. {
    26. for(int i = 0; i < 3; i++)
    27. {
    28. event1.WaitOne();
    29. textBox1.Text += "线程1执行任务,需要1秒完成" + Environment.NewLine;
    30. Thread.Sleep(1000);
    31. event2.Set();
    32. }
    33. }
    34. private void MyTask2()
    35. {
    36. for(int i = 0; i < 3; i++)
    37. {
    38. event2.WaitOne();
    39. textBox1.Text += "线程2执行任务,需要3秒完成" + Environment.NewLine;
    40. Thread.Sleep(3000);
    41. event1.Set();
    42. }
    43. }
    44. }
    45. }

       后面还是要总结详细的多线程通讯技术,包括使用信号量、自旋锁、事件、管道、互斥量、原子操作等等。
      ⑴ 在C#中使用信号量解决多线程访问共享资源的冲突问题
      ⑵ 在C#中使用互斥量解决多线程访问共享资源的冲突问题

  • 相关阅读:
    Vue纯CSS实现掷色子
    论文学习笔记(二):面对多步攻击的网络安全态势评估
    手把手教你下载XShell免费版(超详细)
    【Java网络原理】 四
    【算法】游戏中的学习,使用c#面向对象特性控制游戏角色移动
    南大通用数据库-Gbase-8a-学习-17-Gbase8a集群版本升级
    Mysql查看Binlog文件
    xilinx zynq7系列加载器无法连接的原因&测试xilinx Zynq7开发板的加载器和芯片是否正常的快速方法
    口碑最好的运动蓝牙耳机推荐,2022年最值得入手的六款运动耳机
    【BMS软开系列】1、 ISO 26262功能安全标准 (二)
  • 原文地址:https://blog.csdn.net/dawn0718/article/details/128107337