目录
关于线程、同步、异步的相关知识点
本文不做过多描述,参见下述链接
创建多线程的步骤:
1、编写线程所要执行的方法
2、实例化Thread类,并传入一个指向线程所要执行方法的委托。(这时线程已经产生,但还没有运行)
3、调用Thread实例的Start方法,标记该线程可以被CPU执行了,但具体执行时间由CPU决定
ThreadStart是一个无参的、返回值为void的委托,可以运行静态的方法,也可以运行实例方法
ParameterizedThreadStart是一个有参的、返回值为void的委托,注意若创建有参的方法,注意,方法里面的参数类型必须是Object类型。
使用线程,命名空间开头要加上 using System.Threading;
多线程和异步操作的异同
多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。
更多说明参考下述文章:
C#多线程和异步(三)——一些异步编程模式 - 捞月亮的猴子 - 博客园
委托的BeginInvoke和EndInvoke方法 - canger - 博客园
C# action,delegate,func的用法和区别 - .net&new - 博客园
关于线程池的使用
C#多线程--线程池(ThreadPool) - 从未被超越 - 博客园
利用log4net记录日志
winform项目中使用log4net_hewusheng10的专栏-CSDN博客
关于委托的使用区别:
在C#中实现委托有三种方式:delegate,Action和Func,推荐使用Action和Func
delegate委托使用过程:声明一个委托、实例化一个委托、使用一个委托
Action委托必须是无返回值的方法
Func委托必须有返回值的方法
跨线程操作的案例
下面是跨线程操作的例子,后几种方法不需要提前定义和使用回调,使用更简便
跨线程访问方法1:
//定义用于跨线程的回调
private delegate void SetRichValueCallBack(string value);
//声明回调
SetRichValueCallBack setCallBack;
// 定义回调委托需要执行的方法
private void SetRichValue(string value)
{
richTextBox1.AppendText(value + "\r");
}
private void button7_Click(object sender, EventArgs e)
{
//实例化回调,括号内填回调的方法
setCallBack = new SetRichValueCallBack(SetRichValue);
//创建一个线程去执行这个方法:创建的线程默认是前台线程
Thread t1 = new Thread(new ThreadStart(NumTest1));
//Start方法标记这个线程就绪了,可以随时被执行,具体什么时候执行这个线程,由CPU决定
//将线程设置为后台线程
t1.IsBackground = true;
t1.Start();
Thread t2 = new Thread(new ThreadStart(NumTest2));
t2.IsBackground = true;
t2.Start();
Thread t3 = new Thread(new ThreadStart(NumTest3));
t3.IsBackground = true;
t3.Start();
Thread t4 = new Thread(new ThreadStart(NumTest4));
t4.IsBackground = true;
t4.Start();
Thread t5 = new Thread(new ThreadStart(NumTest5));
t5.IsBackground = true;
t5.Start();
}
private void NumTest1()
{
for (int i= 10;i< 20;i++)
{
//使用回调
Thread.Sleep(500);
richTextBox1.Invoke(setCallBack, i.ToString());
}
}
跨线程访问简化代码1:直观地在Invoke方法调用的时候就看到具体的函数
private void NumTest2()
{
for (int i = 20; i < 30; i++)
{
//使用回调
Thread.Sleep(500);
richTextBox1.Invoke( new EventHandler(delegate
{
richTextBox1.AppendText(i.ToString()+"\r");
}));
}
}
跨线程访问简化代码2:基于简化方法1,richTextBox1.Invoke变为this.Invoke
private void NumTest3()
{
for (int i = 30; i < 40; i++)
{
//使用回调
Thread.Sleep(500);
this.Invoke(new EventHandler(delegate
{
richTextBox1.AppendText(i.ToString() + "\r");
}));
}
}
跨线程访问简化代码3:采用action的写法(推荐)
//定义要执行的方法
private void SetRichValue2(string value)
{
richTextBox1.AppendText(value + "\r");
}
//跨线程访问方法4:action的写法
private void NumTest4()
{
Action<string> action = this.SetRichValue2;
//Action<string> action = new Action<string>(SetRichValue2);//与上面写法相同
for (int i = 40; i < 50; i++)
{
Thread.Sleep(500);
// 调用委托
this.Invoke(action,i.ToString());
}
}
跨线程访问简化代码4:在C#3.0及以后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法
private void NumTest5()
{
for (int i = 50; i < 60; i++)
{
//使用回调
Thread.Sleep(500);
this.Invoke(new Action(()=>
{
richTextBox1.AppendText(i.ToString() + "\r");
}));
}
}
上述代码运行效果如下图
多线程操作案例
多线程同时操作的一个例子:多线程同时图书购买
private int bookNum = 0;
//库存数量设定
private void button9_Click(object sender, EventArgs e)
{
bookNum = int.Parse(textBox1.Text);
richTextBox1.AppendText("库存量" + bookNum.ToString() + "设定成功!" + "\r");
textBox2.Text = bookNum.ToString();
}
//线程1购买图书
private void button3_Click(object sender, EventArgs e)
{
BookBuyThread("线程1");
}
//线程2购买图书
private void button4_Click(object sender, EventArgs e)
{
BookBuyThread("线程2");
}
//多线程购买,根据要设定的数量自动创建多个线程
private void button8_Click(object sender, EventArgs e)
{
int threadNum = int.Parse(textBox3.Text);
if (threadNum >= 1)
{
for (int i = 1; i <= threadNum; i++)
{
BookBuyThread("自动多线程" + i.ToString());
}
}
else
{
richTextBox1.AppendText("多线程数量不合格!" + "\r");
}
}
//定义开启新线程的方法
private void BookBuyThread(string buyerName)
{
Thread t1 = new Thread(new ThreadStart(BookSale));
t1.Name = buyerName;
t1.IsBackground = true;
t1.Start();
}
// 创建有参的方法,注意,方法里面的参数类型必须是Object类型
private void BookSale()
{
//使用lock关键字解决线程同步问题,使得某一时刻只有一个线程可以访问变量
lock (this)
{
string buyerName = Thread.CurrentThread.Name;
if (bookNum > 0)//判断是否有书,如果有就可以卖
{
Thread.Sleep(100);
bookNum -= 1;
this.Invoke(new Action(() =>
{
richTextBox1.AppendText(buyerName + "购买成交!" + "\r");
textBox2.Text = bookNum.ToString();
}));
}
else
{
this.Invoke(new Action(() =>
{
richTextBox1.AppendText(buyerName + "购买失败!库存不足" + "\r");
}));
}
}
}
线程池的使用案例
定义线程池执行的回调方法
//获取线程池结束状态用
AutoResetEvent myEvent = new AutoResetEvent(false);
private int forThreadEnd;
//定义线程池执行的回调方法
private void ThreadPoolCallback(object obj)
{
Thread.Sleep(2000);
log4netHelper.Info(DateTime.Now.ToString() + " 当前线程" + obj.ToString());
//下面代码用于判断线程池内线程是否执行完毕
forThreadEnd -= 1;
if (forThreadEnd == 0)
{
myEvent.Set();
}
}
线程池的使用
private void ThreadPoolTest()
{
int cycleNum = 10;
forThreadEnd = cycleNum;
ThreadPool.SetMinThreads(1,1); //定义线程池最小数量
ThreadPool.SetMaxThreads(5,5); //定义线程池同时执行最大数量
for (int i=1;i<=cycleNum;i++)
{
//把需要执行的方法加入到线程池
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadPoolCallback), i.ToString());
}
richTextBox1.AppendText("主线程开始" + DateTime.Now.ToString() + "\r");
myEvent.WaitOne();
richTextBox1.AppendText("线程池结束"+ DateTime.Now.ToString() + "\r");
}
说明:上述代码执行后,创建一个线程池含10个线程,分2组执行完毕
同步和异步操作案例
同步和异步是对方法执行顺序的描述。
同步:等待上一行完成计算之后,才会进入下一行;
异步:不会等待方法的完成,会直接进入下一行,是非阻塞的;
下面是一个简单的操作案例:
首先定义一些基本参数
//获取当前系统时间
private string GetCurTIme()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
}
//查询当前进程ID值
private int GetThreadID()
{
return Thread.CurrentThread.ManagedThreadId;
}
private void RichInfoAdd(string value)
{
richTextBox1.AppendText(value + "进程编号" + GetThreadID() + " " + GetCurTIme() + "\r");
}
//定义执行次数
private int cycleTime = 3;
//定义要执行的方法
private string TestMethod(string name)
{
Thread.Sleep(1000);
return "DoSL" + name + " end " + "进程编号" + GetThreadID() + " " + GetCurTIme() + "\r";
}
同步操作,所有操作信息一次打印,都在一个线程中执行,时间较长
//同步操作,所有操作信息一次打印,都在一个线程中执行,时间较长
private void button1_Click(object sender, EventArgs e)
{
RichInfoAdd("同步操作start ");
for (int i=0;i<cycleTime;i++)
{
string name="同步操作指令"+i;
richTextBox1.AppendText(TestMethod(name));
}
RichInfoAdd("同步操作end ");
}
异步操作,所有操作信息同时打印,约一个for循环的时间(正确用法)
//异步操作,所有操作信息同时打印,约一个for循环的时间
private void button5_Click(object sender, EventArgs e)
{
RichInfoAdd("异步操作 start ");
Func<string, string> function = this.TestMethod;
IAsyncResult[] result = new IAsyncResult[cycleTime];
//第一个for循环用于创建异步程序
for (int i = 0; i < cycleTime; i++)
{
result[i] = function.BeginInvoke("异步操作指令 " + i, null, null);
}
for (int j = 0; j < cycleTime; j++)
{
richTextBox1.AppendText(function.EndInvoke(result[j]));
}
RichInfoAdd("异步操作 end ");
}
异步操作,下面的代码,虽然操作信息一条一条打印,由于异步开始和接收指令都写在了一个for循环中,总时间与同步操作一样
//异步操作,操作信息一条一条打印,由于异步开始和接收指令都写在了一个for循环中,总时间与同步操作一样
private delegate string AsyncDelegate(string value);
private void button2_Click(object sender, EventArgs e)
{
RichInfoAdd("异步操作方式1 start ");
AsyncDelegate asyncDel =new AsyncDelegate(TestMethod);
for (int i = 0; i < cycleTime; i++)
{
string name = "异步操作指令 " + i;
IAsyncResult result = asyncDel.BeginInvoke(name, null, null);
string returnStr = asyncDel.EndInvoke(result);
richTextBox1.AppendText(returnStr);
}
RichInfoAdd("异步操作方式1 end ");
}
异步操作,与上一条效果相同,不同的是采用func指令
//异步操作,与上一条效果相同,不同的是采用func指令
private void button4_Click(object sender, EventArgs e)
{
RichInfoAdd("异步操作方式2 start ");
Func<string, string> function = this.TestMethod;
for (int i = 0; i < cycleTime; i++)
{
string name = "异步操作指令 " + i;
IAsyncResult result = function.BeginInvoke(name, null, null);
string returnStr = function.EndInvoke(result);
//string returnStr = function.EndInvoke(function.BeginInvoke(name, null, null));
richTextBox1.AppendText(returnStr);
}
RichInfoAdd("异步操作方式2 end ");
}