零、学习目录:
1 进程-线程-多线程,同步和异步
2 委托启动异步调用
3 多线程特点:不卡主线程、速度快、无序性
4 异步的回调和状态参数
5 异步等待三种方式
6 异步返回值
一、进程-线程-多线程,同步和异步
1.什么是进程?
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。
而一个进程又是由多个线程所组成的。
2.什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
3.什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
4.多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
5.多线程的不利方面:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
线程太多会导致控制太复杂,最终可能造成很多 Bug ;6.何时使用多线程?
多线程程序一般被用来在后台执行耗时的任务。主线程保持运行,并且工作线程做它的后台工作。对于Windows Forms程序来说,如果主线程试图执行冗长的操作,键盘和鼠标的操作会变的迟钝,程序也会失去响应。由于这个原因,应该在工作线程中运行一个耗时任务时添加一个工作线程,即使在主线程上有一个有好的提示“处理中...”,以防止工作无法继续。这就避免了程序出现由操作系统提示的“没有相应”,来诱使用户强制结束程序的进程而导致错误。模式对话框还允许实现“取消”功能,允许继续接收事件,而实际的任务已被工作线程完成。BackgroundWorker恰好可以辅助完成这一功能。
在没有用户界面的程序里,比如说Windows Service,多线程在当一个任务有潜在的耗时,因为它在等待另台电脑的响应(比如一个应用服务器,数据库服务器,或者一个客户端)的实现特别有意义。用工作线程完成任务意味着主线程可以立即做其它的事情。
另一个多线程的用途是在方法中完成一个复杂的计算工作。这个方法会在多核的电脑上运行的更快,如果工作量被多个线程分开的话(使用Environment.ProcessorCount属性来侦测处理芯片的数量)。
一个C#程序称为多线程的可以通过2种方式:明确地创建和运行多线程,或者使用.NET framework的暗中使用了多线程的特性——比如BackgroundWorker类, 线程池,threading timer,远程服务器,或Web Services或ASP.NET程序。在后面的情况,人们别无选择,必须使用多线程;一个单线程的ASP.NET web server不是太酷,即使有这样的事情;幸运的是,应用服务器中多线程是相当普遍的;唯一值得关心的是提供适当锁机制的静态变量问题。
7、何时不要使用多线程?
多线程也同样会带来缺点,最大的问题是它使程序变的过于复杂,拥有多线程本身并不复杂,复杂是的线程的交互作用,这带来了无论是否交互是否是有意的,都会带来较长的开发周期,以及带来间歇性和非重复性的bugs。因此,要么多线程的交互设计简单一些,要么就根本不使用多线程。除非你有强烈的重写和调试欲望。
当用户频繁地分配和切换线程时,多线程会带来增加资源和 CPU 的开销。在某些情况下,太多的 I/O 操作是非常棘手的,当只有一个或两个工作线程要比有众多的线程在相同时间执行任务块的多。稍后我们将实现生产者 / 耗费者 队列,它提供了上述功能。二、委托启动异步调用
1.异步介绍:不会等待方法的完成,会直接进入下一行 非阻塞式
2.委托启用:可以定义一个委托
#region Private Method
///
/// 一个比较耗时耗资源的私有方法
///
///
private void DoSomethingLong(string name)
{
Console.WriteLine($"****************DoSomethingLong {name} Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += i;
}
//Thread.Sleep(2000);
Console.WriteLine($"****************DoSomethingLong {name} End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
}
#endregion
3.异步调用:可以在方法里异步调用委托
#region Async
///
/// 异步方法
///
///
///
private void btnAsync_Click(object sender, EventArgs e)
{
Console.WriteLine($"****************btnAsync_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
//{
// Action action = this.DoSomethingLong;
// action.Invoke("btnAsync_Click_1");
// action("btnAsync_Click_2");
// //请 小明 吃饭,大叔说我很忙,然后我就等着你忙完,然后一起去吃饭 诚心诚意 同步方法
// //请 小黄 吃饭,你说你很忙,那我就去吃饭了,你忙完了,你自己去吃饭 客气一下 异步方法
// action.BeginInvoke("btnAsync_Click_3", null, null);
//}
Action action = this.DoSomethingLong;
for (int i = 0; i < 5; i++)
{
//Thread.Sleep(5);
string name = string.Format($"btnAsync_Click_{i}");
action.BeginInvoke(name, null, null);
}
Console.WriteLine($"****************btnAsync_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
三、多线程特点:不卡主线程、速度快、无序性
1.异步多线程方法不卡界面,主线程完事儿了,计算任务交给子线程在做;winform提升用户体验;web一个业务操作后要发邮件,异步发送邮件。
2.异步多线程方法快,因为多个线程并发运算;并不是线性增长,a)资源换时间,可能资源不够 b) 多线程也有管理成本;但并不是越多越好,多个独立任务可以同时运行。
3.异步多线程无序:启动无序 执行时间不确定 结束也无序,一定不要通过等几毫秒的形式来控制启动/执行时间/结束。
四、异步的回调和状态参数
异步的回调是用BeginInvoke,这里它的状态参数有三个,第一个是文本信息,第二个是异步结果的状态,第三个是用户自定义的信息。
#region 异步多线程方法
private void button3_Click(object sender, EventArgs e)
{
Console.WriteLine($"****************异步多线程方法 开始 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Action action = this.DoSomethingLong;
IAsyncResult asyncResult = null;
AsyncCallback callback = new AsyncCallback(ia =>
{
Console.WriteLine(object.ReferenceEquals(asyncResult, ia));
Console.WriteLine(ia.AsyncState);
Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
}
);
asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "mantianxin"); Console.WriteLine($"全部计算真的都完成了,然后给用户返回{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Console.WriteLine($"****************异步多线程方法 结束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
#region 一个比较耗时耗资源的私有方法
private void DoSomethingLong(string name)
{
Console.WriteLine($"****************DoSomethingLong {name} 开始 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
long lResult = 0;
for (int i = 0; i < 1000000000; i++)
{
lResult += i;
}
Console.WriteLine($"****************DoSomethingLong {name} 结束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
五、异步等待三种方式
a).延迟等待;
b).即时等待;
c).异步调用,带返回值;
1.延迟等待:我们平常看到的很多文件下载或者上传有个进度条显示,大多都是用到延迟等待。下面做一个简单的例子模拟文件上传的效果。
代码如下:
#region 异步多线程方法
private void button3_Click(object sender, EventArgs e)
{
Console.WriteLine($"****************异步多线程方法 开始 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
Action action = this.DoSomethingLong;
IAsyncResult asyncResult = null;
AsyncCallback callback = new AsyncCallback(ia =>
{
//Console.WriteLine(object.ReferenceEquals(asyncResult,ia));
//Console.WriteLine(ia.AsyncState);
//Console.WriteLine($"到这里计算已经完成了。{Thread.CurrentThread.ManagedThreadId.ToString("00")}。");
}
);
asyncResult = action.BeginInvoke("btnAsyncAdvanced_Click", callback, "mantianxin");
// Console.WriteLine($"全部计算真的都完成了,然后给用户返回{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
#region 模拟文件上传,但是存在着延迟
int i = 0;
while (!asyncResult.IsCompleted)//表示异步操作有没有完成,这里是没有完成;会卡界面:主线程忙于等待
{
if (i < 10)
{
Console.WriteLine($"文件上传完成{i++ * 10}%...");//File.ReadSize
}
else
{
Console.WriteLine($"文件上传完成99.9%...");
}
Thread.Sleep(200);
}
Console.WriteLine($"文件上传成功!");
#endregion
Console.WriteLine($"全部计算真的都完成了,然后给用户返回{Thread.CurrentThread.ManagedThreadId.ToString("00")}");
Console.WriteLine($"****************异步多线程方法 结束 {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
}
#endregion
2.即时等待:即时等待可以防止页面(UI)假死。
代码如下:
#region 即时等待
Thread.Sleep(200);
Console.WriteLine("Do Something Else...");
Console.WriteLine("Do Something Else...");
Console.WriteLine("Do Something Else...");
//asyncResult.AsyncWaitHandle.WaitOne();//等待任务的完成!
//asyncResult.AsyncWaitHandle.WaitOne(-1);//等待任务的完成!
asyncResult.AsyncWaitHandle.WaitOne(1000);//限时等待,需求:请求超时功能!
#endregion
3.异步调用,带返回值:可以在异步的时候,带上想要的返回值
代码如下:
#region 异步调用,带返回值
{
Func func = () =>
{
Thread.Sleep(200);
return DateTime.Now.Day;
};
Console.WriteLine($"func.Invoke()={func.Invoke()}");
//委托的回调(异步调用)
IAsyncResult async = func.BeginInvoke(n =>
{
Console.WriteLine(n.AsyncState);
}, "mantianxin");
Console.WriteLine($"func.EndInvoke(asyncResult)={func.EndInvoke(async)}");
}
#endregion
六、异步返回值