C# 学习笔记线程
Posted 不咸不要钱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 学习笔记线程相关的知识,希望对你有一定的参考价值。
C# 学习笔记(9)线程
本文参考博客
C#多线程 https://www.cnblogs.com/dotnet261010/p/6159984.html
C# 线程与进程 https://www.cnblogs.com/craft0625/p/7496682.html
C# 线程同步https://www.cnblogs.com/dotnet261010/p/12335538.html
对于c#中的线程和进程,这几篇文章讲的相当到位了,本文只是为了学习做的摘要。
线程间数据同步
看下面一个例子,有线程t1和t2 都操作变量 Counter 自增一千次,理论上我们最后应该得到 Counter = 2000;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private static int Counter = 0;
private void btnPrint_Click(object sender, EventArgs e)
{
Counter = 0;
Thread t1 = new Thread(() => {
for (int i = 0; i < 1000; i++)
{
Counter++;
Thread.Sleep(1);
}
});
t1.Start();
Thread t2 = new Thread(() => {
for (int i = 0; i < 1000; i++)
{
Counter++;
Thread.Sleep(1);
}
});
t2.Start();
//等待线程结束
while (!(t1.ThreadState == ThreadState.Stopped && t2.ThreadState == ThreadState.Stopped)) ;
txbLog.AppendText(Counter.ToString() + "\\r\\n");
}
}
但是实际上你会发现,线程t1和线程t2结束后 Counter的值并不会是预期的2000,而且还会有波动,为什么?
假设t1线程读取到Counter的值为200,可能t2线程执行非常快,t1线程读取Counter值的时候,t2线程已经把Counter的值改为了205,等t1线程执行完毕以后,Counter的值又被变为了201,这样就会出现线程同步的问题了。那么该如何解决这个问题呢?
lock
lock是C#中的关键字,可以锁定一个资源,lock的特点是:同一时刻只能有一个线程进入lock的对象的范围,其它lock的线程都要等待。如果研究嵌入式,其实就是嵌入式RTOS中的互斥信号量
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private static int Counter = 0;
static object loker = new object();
private void btnPrint_Click(object sender, EventArgs e)
{
Counter = 0;
Thread t1 = new Thread(() => {
for (int i = 0; i < 1000; i++)
{
lock(loker)
{
Counter++;
}
Thread.Sleep(1);
}
});
t1.Start();
Thread t2 = new Thread(() => {
for (int i = 0; i < 1000; i++)
{
lock (loker)
{
Counter++;
}
Thread.Sleep(1);
}
});
t2.Start();
//等待线程结束
while (!(t1.ThreadState == ThreadState.Stopped && t2.ThreadState == ThreadState.Stopped)) ;
txbLog.AppendText(Counter.ToString() + "\\r\\n");
}
}
添加lock后 结果符合预期
同步方法
除了使用关键字 lock外 还可以将方法标记为同步方法
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private static int Counter = 0;
static object loker = new object();
//将方法标记为同步方法
[MethodImpl(MethodImplOptions.Synchronized)]
static void Test()
{
Counter++;
}
private void btnPrint_Click(object sender, EventArgs e)
{
Counter = 0;
Thread t1 = new Thread(() => {
for (int i = 0; i < 1000; i++)
{
Test();
Thread.Sleep(1);
}
});
t1.Start();
Thread t2 = new Thread(() => {
for (int i = 0; i < 1000; i++)
{
Test();
Thread.Sleep(1);
}
});
t2.Start();
//等待线程结束
while (!(t1.ThreadState == ThreadState.Stopped && t2.ThreadState == ThreadState.Stopped)) ;
txbLog.AppendText(Counter.ToString() + "\\r\\n");
}
}
利用委托自动创建线程
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//声明委托类
delegate void txbLogPrintDelegate();
void PrintfLog()
{
for (int i = 0; i < 10000; i++)
{
//通过txbLog的InVoke 告诉创建txbLog的线程,需要操作txbLog控件
txbLog.Invoke((Action<string>)TxbLogAppendText, "这是第" + i + "行\\r\\n");
Thread.Sleep(1);
}
}
void TxbLogAppendText(string str)
{
//创建txbLog的线程也就是主线程 操作txbLog控件
this.txbLog.AppendText(str);
}
private void btnPrint_Click(object sender, EventArgs e)
{
创建线程
//Thread printThread = new Thread(PrintfLog);
告诉系统,这个线程准备好了,可以开始执行了,至于什么时候执行,看系统安排
//printThread.Start();
//通过委托创建线程
txbLogPrintDelegate testDelegate = PrintfLog;
testDelegate.BeginInvoke(null, null);
}
}
同步与异步
利用委托自动创建线程时 有 BeginInvoke 和 Invoke两种,区别就是 Invoke是同步的,当工作线程运行结束才会执行下一行代码,BeginInvoke 是异步的。
带参数线程
使用thread 创建的线程,参数只能是 object类型的,object是所有类型的基类,因此可以装其他的所有类型,不过需要自己进行类型转换。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//声明委托类
delegate void txbLogPrintDelegate(int num);
void PrintfLog(object num)
{
for (int i = 0; i < (int)num; i++)
{
//通过txbLog的InVoke 告诉创建txbLog的线程,需要操作txbLog控件
txbLog.Invoke((Action<string>)TxbLogAppendText, "这是第" + i + "行\\r\\n");
Thread.Sleep(1);
}
}
void TxbLogAppendText(string str)
{
//创建txbLog的线程也就是主线程 操作txbLog控件
this.txbLog.AppendText(str);
}
private void btnPrint_Click(object sender, EventArgs e)
{
//创建线程
Thread printThread = new Thread(PrintfLog);
//告诉系统,这个线程准备好了,可以开始执行了,至于什么时候执行,看系统安排
printThread.Start(1000);
通过委托创建线程
//txbLogPrintDelegate testDelegate = PrintfLog;
//testDelegate.BeginInvoke(100, null, null);
}
}
回调
当委托创建的异步线程执行结束时,想要在最后添加一个操作,可以利用异步委托的回调来实现(当然也可以将该操作添加到异步线程结束前,但是这样有可能会破坏模块封装性)。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//声明委托类
delegate void txbLogPrintDelegate(int num);
void PrintfLog(int num)
{
for (int i = 0; i < (int)num; i++)
{
//通过txbLog的InVoke 告诉创建txbLog的线程,需要操作txbLog控件
txbLog.Invoke((Action<string>)TxbLogAppendText, "这是第" + i + "行\\r\\n");
Thread.Sleep(1);
}
}
void TxbLogAppendText(string str)
{
//创建txbLog的线程也就是主线程 操作txbLog控件
this.txbLog.AppendText(str);
}
private void btnPrint_Click(object sender, EventArgs e)
{
//通过委托创建线程
txbLogPrintDelegate testDelegate = PrintfLog;
//创建回调函数 异步线程结束时会调用该回调函数
AsyncCallback asyncCallback = p => { txbLog.Invoke((Action<string>)TxbLogAppendText, "回调函数 结束\\r\\n"); };
testDelegate.BeginInvoke(100, asyncCallback, null);
}
}
以上是关于C# 学习笔记线程的主要内容,如果未能解决你的问题,请参考以下文章