了解什么是进程、线程 、多线程:
计算机概念:
进程:程序在服务器上运行时占据的计算资源集合称之为进程;
进程之间不会相互影响/干扰—— 但是 进程之间的通讯比较困难(分布式)
线程:程序执行的最小单位,响应操做的最小的执行流;
线程也包含自己的计算资源
线程属于进程的,一个进程可以有多个线程;
多线程: 一个进程种有多个线程并发执行
C#
多线程 Thread 类, 就是一个封装 是netFrameork对线程对象的抽象封装
通过 Thread 完成的操做,最终通过向操做系统请求得到执行流;
currentThread 表示当前线程——任何操做执行都是线程完成的,运行当前这句话的线程
managed ThreadId: 是. Net 平台给 Thread起的名字 , 就是个 int 值, 尽量不重复
异步多线程区别与联系:
线程相关的内容介绍:
Thread:
currentThread : 是指当前正在运行的代码进程
Thread.CurrentThread.ManagedThreadId 当前正在运行的进程的编号:
下面是程序进行验证:
主要的步骤是 先定义一个比较耗时的函数,名称为: DoSomthingLong
函数的写法如下:
private void DoSomthingLong(string name)
{
Console.WriteLine("***********DoSomeThigLong Start {0} {1} {2} ************",
name ,Thread.CurrentThread.ManagedThreadId.ToString("00"),DateTime.Now.ToString("HHmmss:fff"));
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += 1;
}
Console.WriteLine("***********DoSomeThigLong Start {0} {1} {2} (3) ************",
name, Thread.CurrentThread.ManagedThreadId.ToString("00"), DateTime.Now.ToString("HHmmss:fff"),lResult);
}
然后 使用 xmal 文件定义 两个按钮, 同步方法 、 异步方法 ; 然后绑定两个动作
同步方法绑定动作为:
private void btnSync_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine();
Console.WriteLine("***********btnSync_Click 同步方法 start {0} *************", Thread.CurrentThread.ManagedThreadId);
for (int i = 0; i < 5; i++)
{
string name = string.Format("{0}_{1}", "btnSync_Click", i);
this.DoSomthingLong(name);
}
Console.WriteLine("***********btnSync_Click 同步方法 end {0} *************", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine();
}
同步的方法测试特点;
按照顺序执行,单线程,一个执行结束后才可以执行下一个
异步多线程测试:
任何的异步多线程都离不开委托 delegate——lambda ——action / func
代码如下:
private void btnAsync_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine();
Console.WriteLine("***********btnSync_Click 异步方法 start {0} *************", Thread.CurrentThread.ManagedThreadId);
Action<string> action = this.DoSomthingLong;
action.Invoke("btnAsync_Click_1");
action("btnAsync_Click_2");
action.BeginInvoke("btnAsync_Click_3",null,null);
Console.WriteLine("***********btnSync_Click 异步方法 end {0} *************", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine();
}
异步多线程的特点:
没有执行完就进入了下一行,但是动作会有一个新的线程来执行
多线程写法不难,但是用好很难
01 是主线程, 03 是子线程
总结:同步 和异步的差别
同步使用的是 action,invoke
异步使用的是 action.beginInvoke
特点:
同步是按照顺序一次进行执行的,并且会导致UI界面卡住,无法拖动(卡住的原因是主线程忙于计算,没有多余的算例更新UI)
异步的话 执行没有顺序,并且是同时开始的
并且不会卡住界面:
异步回调相关需要了解知识点:
异步的回调和状态参数
异步等待三种方式
获取异步的返回值
多线程的不可预测性:
由于多线程具有随机启动和不可预测性,在很多时候对业务有影响
所以使用多线程也要解决多线程的不可预测性
不允许没有监控的项目上线 —— 需要在业务操做后记录日志
但是异步方法的日志不可以直接 写在主线程中,因为这个会在进程还没有执行完就会打印日志
所以这里需要使用异步调用的回调功能, 异步调用的回调功能语法如下:
回调函数编写方法
AsyncCallback callback3 = ar =>
{
// 获取传入的参数
string inputparam = ar.AsyncState.ToString();
Console.WriteLine("回调函数:btnAsyncAdvanced_Click 操已经完成了{0},传入参数为:{1}", Thread.CurrentThread.ManagedThreadId,inputparam);
};
如何调用该异步多线程:
action.BeginInvoke(name, callback3, "传入字符");
有的时候需要知道当前的线程有没有执行结束,这个时候可以使用 :
action 的返回值:IAsyncResult 类型的 并且判断其属性
IAsyncResult asyncResult= action.BeginInvoke(name, callback3, "传入字符");
int i=0;
while (!asyncResult.IsCompleted)
{
Thread.Sleep(200);
i +=1;
Console.WriteLine($" ******** 计数器现在计数为:{i}***********");
}
需要注意一点就是上面的有延时的,所以如何避免上面200 ms 的延时?
如果不想通过延时的方法 得到状态的变化,可以使用
asyncResult.AsyncWaitHandle.WaitOne(); // 阻塞当前的进程,等待信号完成
asyncResult.AsyncWaitHandle.WaitOne(-1); // 一直等待
asyncResult.AsyncWaitHandle.WaitOne(1000); // 等待且只等待1000ms 超过就跳过了
调用接口如何获取返回值:
首先建立一个函数用来模拟远程 的接口
// 模拟远程接口
private int RemoteService()
{
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += 1;
}
return DateTime.Now.Day;
}
如果是使用同步调用接口的返回值可以使用如下方法:
同步方法 获得远程接口的返回值的方式:
int iResult1 = this.RemoteService();
Func<int> func = this.RemoteService;
int iResult2 = func.Invoke();
使用异步方式获得调用接口的返回值的方式:
// 异步调用如何获得真实函数的返回值
IAsyncResult asyncResult= func.BeginInvoke(null, null); // 异步调用结果 描述异步操作的
int iresult3 = func.EndInvoke(asyncResult);
当然如果需要调用传入参数的远程接口可以按照下面两个方式进行填写:
直是案例使用的是匿名函数
{
Func<string> func1 = ()=> DateTime.Now.ToString();
string sResult = func1.EndInvoke(func1.BeginInvoke(null,null)); // 没有传入参数的 远程接口的调用
Func<string , string> func2 = s => $"1+{s}";
string sResult2 = func2.EndInvoke(func2.BeginInvoke("sss",null, null)); // 有传入参数的远程接口的调用
}
当然异步调用的返回值获取也可以在 回调中获取,但是需要注意一点, 在回调中如果使用到了
endInvoke 在外面就不可以使用了,
注意: 也就是说 对于远程调用接口的获取值, endInvoke 只能使用一次
关于frameWork 中的线程的写法:
netFrame Work 中的线程的写法:
在net frame 中线程的使用方式很多,下面针对不同版本的net frame work 来说明对应的进程是如何写的:
netframework1.0 1.1
// netframeWork 1.0 1.1 进程的使用方式
ThreadStart threadStart = () =>
{
Console.WriteLine($"this is thread start {Thread.CurrentThread.ManagedThreadId} ");
Thread.Sleep(2000);
Console.WriteLine($"this is thread end {Thread.CurrentThread.ManagedThreadId} ");
};
Thread thread = new Thread(threadStart);
Thread 的优缺点
有点 Thread 的功能特别丰富,但是使用的不好容易导致问题
netframe worK2.0 Threadpool
// netframeWork2.0
//ThreadPool 是一种池化资源管理的设计思想
// 线程是一种资源 之前每次要用线程就去申请一个线程 使用之后释放掉 ; 池化就是容器 容器提前申请
// 5个线程 ,如果程序再需要线程的话就不用去操做系统索要线程了 用完后再放回容器 避免频繁申请和销毁
// 容器自己也会根据线程的数量 申请和释放
WaitCallback callback = o =>
{
Console.WriteLine($"this is ThreadPool start {Thread.CurrentThread.ManagedThreadId} ");
Thread.Sleep(2000);
Console.WriteLine($"this is ThreadPool end {Thread.CurrentThread.ManagedThreadId} ");
};
ThreadPool.QueueUserWorkItem(callback);
缺点,线程之间的顺序控制比较难
net framework 3.0 使用的是 TasK
// netframework Task
Action action = () =>
{
Console.WriteLine($"this is ThreadPool start {Thread.CurrentThread.ManagedThreadId} ");
Thread.Sleep(2000);
Console.WriteLine($"this is ThreadPool end {Thread.CurrentThread.ManagedThreadId} ");
};
Task task = new Task(action);
task.Start();
Parallel 可以启动多个线程 主线程也参与计算 ,节约一个线程
可以通过parallelOptions 轻松控制最大的并发数量
// 并行 线程的使用
//Parallel 可以启动多个线程 主线程也参与计算 ,节约一个线程
//可以通过parallelOptions 轻松控制最大的并发数量
// 因为主线程参与计算所以会卡住界面
Parallel.Invoke(
()=>
{
//并行中的一个进程
Console.WriteLine($"this is Parallel start {Thread.CurrentThread.ManagedThreadId} ");
Thread.Sleep(2000);
Console.WriteLine($"this is Parallel end {Thread.CurrentThread.ManagedThreadId} ");
},
()=>
{
// 并行中的另一个进程
Console.WriteLine($"this is Parallel start {Thread.CurrentThread.ManagedThreadId} ");
Thread.Sleep(2000);
Console.WriteLine($"this is Parallel end {Thread.CurrentThread.ManagedThreadId} ");
});
当需要写很多个Task 的时候可以使用 List 将需要写的
// 使用taskLIst
List<Task> taskList = new List<Task>();
taskList.Add(Task.Run(() => this.Coding("1", "")));
taskList.Add(Task.Run(() => this.Coding("2", "")));
taskList.Add(Task.Run(() => this.Coding("3", "")));
// 等待所有的线程结束
Task.WaitAll(taskList.ToArray());
//等待其中一个结束
Task.WaitAny(taskList.ToArray());
其中使用的 Coding 函数的写法如下:
private void Coding(string name,string var)
{
Console.WriteLine("***********DoSomeThigLong Start {0} {1} {2} ************",
name, Thread.CurrentThread.ManagedThreadId.ToString("00"), DateTime.Now.ToString("HHmmss:fff"));
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += 1;
}
Console.WriteLine("***********DoSomeThigLong Start {0} {1} {2} (3) ************",
name, Thread.CurrentThread.ManagedThreadId.ToString("00"), DateTime.Now.ToString("HHmmss:fff"), lResult);
}
但是需要注意一点,按照上面的写法 会出现卡UI界面的情况,所以需要让UI不卡顿可以有下面几个方法:
1、 将wait any wait all 放到一个新的线程里面(但是这个方法不是很推荐,因为线程内部不要套线程)
2、 使用 新的API TaskFactory
TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAll(taskList.ToArray(),t=>
{
Console.WriteLine("所有的线程结束~");
}
);
taskFactory.ContinueWhenAny(taskList.ToArray(), t =>
{
Console.WriteLine("有一个线程结束了~");
}
);
最后需要注意 TaskFactory 也是Task d的类型 也可以放到taskList 中
多线程相关的命令
Thread、ThreadPool、Task、Async、Invoke、await
Thread的使用方式
Thread的使用可以参考官方的连接:
Thread 类 (System.Threading) | Microsoft Docs
简单的线程处理功能
这里为了演示,使用wpf 的界面整合,不过还是使用的click 耦合代码进行演示:
Xaml的前端代码是:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button x:Name="myButton" Content="简单线程按钮" Background="Gray" Width=" 100" Height="37"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10 10 0 0 "
Click="myButton_Click"/>
</Grid>
如图:
后台代码中先要建立一个线程的函数,这里命名为ThreadProc
public void ThreadProc()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine("ThreadProc{0}", i);
//向外输出次数
Thread.Sleep(0);
}
}
然后当触发按钮之后,会创建一个新的线程并执行
private void myButton_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("主进程开启第二进程");
//thread 类需要一个委托函数 ThreadStart 来表示在线程上要执行的内容
//Thread t = new Thread(new ThreadStart(ThreadProc));
Thread t = new Thread(ThreadProc);
t.Start();
}
需要注意这里的 thread t 有两种生成的方式均可。
向进程函数中传入变量
上面我们可以看到,进程函数没有传入参数,如果我们需要向进程中传入参数的话,可以参照下面的代码:
public void ThreadProc(object obj)
{
for (int i = 0; i < (int)obj; i++)
{
Console.WriteLine("ThreadProc{0}", i);
//向外输出次数
Thread.Sleep(0);
}
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
Console.WriteLine("主进程开启第二进程");
//thread 类需要一个委托函数 ThreadStart 来表示在线程上要执行的内容
Thread t = new Thread(ThreadProc);
t.Start(120);
}