C#作为一门优秀的开发语言,现在国内的流行度貌似不如以前,大家都不在意它的无所不能了。
C#的灵活与强大只有在经常使用中才会有所领悟,适当地掌握它还是有必要的。
在这里总结一下线程的传递参数以及获取线程的返回值,还有处理多线程之间可能引发的冲突以及解决办法。
在C#中,开启一个线程很容易。
Thread Th1= new Thread(func);
Th1.Start();
private void func(object Obj)
{
//处理代码
}
很多情况下,我们是需要对线程进行传递参数的,这个也简单。
1、线程的单一参数传递
private void button1_Click(object sender, EventArgs e)
{
Thread Th1= new Thread(func);
Th1.Start("CSDN");
Thread.Sleep(500);
}
private void func(object Obj)
{
string Str = Obj as string;
textBox1.BeginInvoke(new Action(() =>
{
textBox1.Text = $"传入的参数:{Str}";
}));
}
2、线程的多参数传递以及返回值
上面的例子是单一的参数,参数要求是对象,使用的时候进行了拆箱,根据上面的例子对于多参数,可以使用中间对象来处理,就是在中间对象中放置参数和获取处理后的结果。
private void button1_Click(object sender, EventArgs e)
{
FinancialInfo FI=new FinancialInfo();
FI.PersonName = "CSDN";
FI.PersonDeposit = 123;
Thread Th1 = new Thread(FI.ThreadChangeFinanceialInfo);
Th1.Start();
Thread.Sleep(500);
textBox1.Text=FI.PersonName+Environment.NewLine+FI.PersonDeposit.ToString();
}
private class FinancialInfo
{
private string Name=string.Empty;
private int Deposit=0;
public string PersonName
{
get { return Name; }
set { Name = value; }
}
public int PersonDeposit
{
get { return Deposit; }
set { Deposit = value; }
}
public void ThreadChangeFinanceialInfo()
{
this.Name = this.Name + " | C#";
this.Deposit = this.Deposit + 100;
}
}
3、使用Task.Result获取任务返回值
上面的方法看似比较繁琐,使用Task则比较简单。
FinancialInfo fi=new FinancialInfo();
fi.PersonName = "CSDN";
fi.PersonDeposit = 123;
var task1 = Task.Factory.StartNew( fi.ThreadChangeFinanceialInfoResult );
fi = task1.Result;
textBox1.Text = textBox1.Text + fi.PersonName +"====="+fi.PersonDeposit.ToString()+Environment.NewLine;
FinancialInfo定义与上面一样。
private class FinancialInfo
{
private string Name=string.Empty;
private int Deposit=0;
public string PersonName
{
get { return Name; }
set { Name = value; }
}
public int PersonDeposit
{
get { return Deposit; }
set { Deposit = value; }
}
public void ThreadChangeFinanceialInfo()
{
this.Name = this.Name + " | C#";
this.Deposit = this.Deposit + 100;
}
public FinancialInfo ThreadChangeFinanceialInfoResult()
{
FinancialInfo fi=new FinancialInfo();
fi.Name = this.Name + " | C#";
fi.Deposit = this.Deposit + 100;
return fi;
}
}
上面的写法就好看多了。
4、多线程可能引起的冲突以及解决办法
多线程在处理同一对象时容易引起潜在的冲突,这个显而易见,例如:
private void button1_Click(object sender, EventArgs e)
{
FinancialInfo FI = new FinancialInfo();
FI.PersonName = "CSDN";
FI.PersonDeposit = 123;
Thread Th1 = new Thread(FI.ThreadAdd);
Thread Th2 = new Thread(FI.ThreadReduce);
Th1.Start();
Th2.Start();
Thread.Sleep(5000);
textBox1.Text = textBox1.Text + FI.PersonName +"|"+FI.PersonDeposit.ToString()+Environment.NewLine;
}
private class FinancialInfo
{
private string Name=string.Empty;
private int Deposit=0;
public string PersonName
{
get { return Name; }
set { Name = value; }
}
public int PersonDeposit
{
get { return Deposit; }
set { Deposit = value; }
}
public void ThreadAdd()
{
for (int i = 0; i < 1000000; i++)
{
this.Deposit = this.Deposit + 1;
}
}
public void ThreadReduce()
{
for (int i = 0; i < 1000000; i++)
{
this.Deposit = this.Deposit - 1;
}
}
}
显示结果:
按道理, FI.PersonDeposit的值是123,加了1000000,也减了1000000,那么最终的结果应该还是123,为什么会是这样呢?
这就是多线程在处理同一对象时所产生的冲突了,产生的就是所谓的“脏数据”。
上面的代码因为等待线程执行完,进行了休眠,可以使用Task来写更简单。
var task1 = new Task(FI.ThreadAdd);
var task2 = new Task(FI.ThreadReduce);
task1.Start();
task2.Start();
Task.WaitAll(task1,task2);
Task是比Thread更加高级的概念,一个Task至少包含一个Thread。
解决上面的冲突就是对可能引起冲突的对象进行加锁判断。
完整代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Text;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MultiThread
{
public partial class Form3 : Form
{
private static readonly object LockObj=new object();
public Form3()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
FinancialInfo FI = new FinancialInfo();
FI.PersonName = "CSDN";
FI.PersonDeposit = 123;
var task1 = new Task(FI.ThreadAdd);
var task2 = new Task(FI.ThreadReduce);
task1.Start();
task2.Start();
Task.WaitAll(task1, task2);
textBox1.Text = textBox1.Text + FI.PersonName +"|"+FI.PersonDeposit.ToString()+Environment.NewLine;
}
private class FinancialInfo
{
private string Name=string.Empty;
private int Deposit=0;
public string PersonName
{
get { return Name; }
set { Name = value; }
}
public int PersonDeposit
{
get { return Deposit; }
set { Deposit = value; }
}
public void ThreadChangeFinanceialInfo()
{
this.Name = this.Name + " | C#";
this.Deposit = this.Deposit + 100;
}
public void ThreadAdd()
{
for (int i = 0; i < 1000000; i++)
{
lock(LockObj)
{
this.Deposit = this.Deposit + 1;
}
}
}
public void ThreadReduce()
{
for (int i = 0; i < 1000000; i++)
{
lock(LockObj)
{
this.Deposit = this.Deposit - 1;
}
}
}
}
}
}
显示结果:
上面显示出了正确的结果,但是会耗时。
5、多线程之间的协调
在多线程需要独占访问资源的时候,AutoResetEvent 允许线程通过发信号互相通信。
通过 WaitOne 来等待信号,调用 Set 发出资源可用的信号。
这个使用比较简单。
举例让两个线程交替执行。
下面是完整的代码。
using System;
using System.Threading;
namespace MultiThread2023
{
public partial class Form1 : Form
{
static AutoResetEvent event1 = new AutoResetEvent(false);
static AutoResetEvent event2 = new AutoResetEvent(false);
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text += "主线程启动"+Environment.NewLine;
Thread t1 = new Thread(MyTask1);
Thread t2 = new Thread(MyTask2);
t1.Start();
t2.Start();
textBox1.Text += "线程1执行" + Environment.NewLine;
event1.Set();
}
private void MyTask1()
{
for(int i = 0; i < 3; i++)
{
event1.WaitOne();
textBox1.Text += "线程1执行任务,需要1秒完成" + Environment.NewLine;
Thread.Sleep(1000);
event2.Set();
}
}
private void MyTask2()
{
for(int i = 0; i < 3; i++)
{
event2.WaitOne();
textBox1.Text += "线程2执行任务,需要3秒完成" + Environment.NewLine;
Thread.Sleep(3000);
event1.Set();
}
}
}
}
后面还是要总结详细的多线程通讯技术,包括使用信号量、自旋锁、事件、管道、互斥量、原子操作等等。
⑴ 在C#中使用信号量解决多线程访问共享资源的冲突问题
⑵ 在C#中使用互斥量解决多线程访问共享资源的冲突问题