在计算机中,一个线程就是一系列的命令,一个工做单元。操做系统能够管理多个线程,给每一个线程分配cpu执行的时间片,而后切换不一样的线程在这个cpu上执行。这种单核的处理器一次只能作一件事,不能同时作两件以上的事情,只是经过时间的分配来实现多个线程的执行。可是在多核处理器上,能够实现同时执行多个线程。操做系统能够将时间分配给第一个处理器上的线程,而后在另外一个处理器上分配时间给另外一个线程。编程
异步是相对于同步而言。跟多线程不能同一而论。服务器
异步编程采用future或callback机制,以免产生没必要要的线程。(一个future表明一个将要完成的工做。)异步编程核心就是:启动了的操做将在一段时间后完成。这个操做正在执行时,不会阻塞原来的线程。启动了这个操做的线程,能够继续执行其余任务。当操做完成时,会通知它的future或者回调函数,以便让程序知道操做已经结束。多线程
为何要使用异步:异步
面向终端用户的GUI程序:异步编程提升了相应能力。可使程序在执行任务时仍能相应用户的输入。
服务器端应用:实现了可扩展性。服务器应用能够利用线程池知足其可扩展性。async
若是以同步方式执行某个任务时,须要等待该任务完成,而后才能再继续执行另外一个任务。而用异步执行某个任务时,能够在该任务完成以前执行另外一个任务。异步最重要的体现就是不排队,不阻塞。异步编程
图:单线程同步
函数
图:多线程同步
操作系统
异步能够在单个线程上实现,也能够在多个线程上实现,还能够不须要线程(一些IO操做)。线程
图:单线程异步
3d
图:多线程异步
异步能够分为CPU异步和IO异步。异步在CPU操做中是必需要跑在线程上的,通常状况下这时咱们都会新开一个线程执行这个异步操做。但在IO操做中是不须要线程的,硬件直接和内存操做。
可是是否建立线程取决于你的异步的实现方式。好比在异步你用ThreadPool,Task.Run()等方法是建立了一个线程池的线程,那么该异步是在另外一个线程上执行。
C#实现异步的四种方式:
Task
的异步async
,await
关键字异步异步模式是调用Beginxxx
方法,返回一个IAsyncResult
类型的值,在回调函数里调用Endxxxx(IAsyncResult)
获取结果值。
异步模式中最多见的是委托的异步。
如:声明一个string类型输入参数和string类型返回值的委托。调用委托的BeginInvoke方法,来异步执行该委托。
Func<string, string> func = (string str) => { Console.WriteLine(str); return str + " end"; }; func.BeginInvoke("hello",IAsyncResult ar => { Console.WriteLine(func.EndInvoke(ar)); }, null); //输出: //hello //hello end
BeginInvoke
方法的第一个参数表示委托的输入参数。
第二个参数表示IAsyncResult
类型输入参数的回调函数,其实也是个委托。
第三个参数是个状态值。
事件异步有一个xxxAsync
方法,和对应该方法的 xxxCompleted
事件。
如: backgroundworker
和progressbar
结合
public partial class MainWindow : Window { private BackgroundWorker bworker = new BackgroundWorker(); public MainWindow() { InitializeComponent(); //支持报告进度 bworker.WorkerReportsProgress = true; //执行具体的方法 bworker.DoWork += Bworker_DoWork; //进度变化时触发的事件 bworker.ProgressChanged += Bworker_ProgressChanged; //异步结束时触发的事件 bworker.RunWorkerCompleted += Bworker_RunWorkerCompleted; Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { //开始异步执行 bworker.RunWorkerAsync(); } private void Bworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //异步完成时触发的事件 progressBar.value=100; } private void Bworker_ProgressChanged(object sender, ProgressChangedEventArgs e) { //获取进度值复制给progressBar progressBar.Value = e.ProgressPercentage; } private void Bworker_DoWork(object sender, DoWorkEventArgs e) { for (int j = 0; j <= 100; j++) { //调用进度变化方法,触发进度变化事件 bworker.ReportProgress(j); Thread.Sleep(100); } } }
Task
是在Framework4.0提出来的新概念。Task
自己就表示一个异步操做(Task
默认是运行在线程池里的线程上)。它比线程更轻量,能够更高效的利用线程。而且任务提供了更多的控制操做。
任务的执行默认是由任务调度器来实现的(任务调用器使这些任务并行执行)。任务的执行和线程不是一一对应的。有可能会是几个任务在同一个线程上运行,充分利用了线程,避免一些短期的操做单独跑在一个线程里。因此任务更适合CPU密集型操做。
任务能够赋值当即运行,也能够先由构造函数赋值,以后再调用。
//启用线程池中的线程异步执行 Task t1 = Task.Factory.StartNew(() => { Console.WriteLine("Task启动..."); }); //启用线程池中的线程异步执行 Task t2 = Task.Run(() => { Console.WriteLine("Task启动..."); }); Task t3 = new Task(() => { Console.WriteLine("Task启动..."); }); t3.Start();//启用线程池中的线程异步执行 t3.RunSynchronously();//任务同步执行
Task t1 = Task.Run(() => { Console.WriteLine("Task启动..."); }); Task t2 = Task.Run(() => { Console.WriteLine("Task启动..."); }); //调用WaitAll() ,会阻塞调用线程,等待任务执行完成 ,这时异步也没有意义了 Task.WaitAll(new Task[] { t1, t2 }); Console.WriteLine("Task完成..."); //调用ContinueWith,等待任务完成,触发下一个任务,这个任务可看成任务完成时触发的回调函数。 //为了获取结果,同时不阻塞调用线程,建议使用ContinueWith,在任务完成后,接着执行一个处理结果的任务。 t1.ContinueWith((t) => { Console.WriteLine("Task完成..."); }); t2.ContinueWith((t) => { Console.WriteLine("Task完成..."); }); //调用GetAwaiter()方法,获取任务的等待者,调用OnCompleted事件,当任务完成时触发 //调用OnCompleted事件也不会阻塞线程 t1.GetAwaiter().OnCompleted(() => { Console.WriteLine("Task完成..."); }); t2.GetAwaiter().OnCompleted(() => { Console.WriteLine("Task完成..."); });
//实例化一个取消实例 var source = new CancellationTokenSource(); var token = source.Token; Task t1 = Task.Run(() => { Thread.Sleep(2000); //判断是否任务取消 if (token.IsCancellationRequested) { //token.ThrowIfCancellationRequested(); Console.WriteLine("任务已取消"); } Thread.Sleep(500); //token传递给任务 }, token); Thread.Sleep(1000); Console.WriteLine(t1.Status); //取消该任务 source.Cancel(); Console.WriteLine(t1.Status);
Task<string> t1 = Task.Run(() => TaskMethod("hello")); t1.Wait(); Console.WriteLine(t1.Result); public string TaskMethod(string str) { return str + " from task method"; }
Task异步操做,须要注意的一点就是调用Waitxxx方法,会阻塞调用线程。
首先要明确一点的就是async
await
不会建立线程。而且他们是一对关键字,必须成对的出现。
若是await
的表达式没有建立新的线程,那么一个异步操做就是在调用线程的时间片上执行,不然就是在另外一个线程上执行。
async Task MethodAsync() { Console.WriteLine("异步执行"); await Task.Delay(4000); Console.WriteLine("异步执行结束"); }
一个异步方法必须有async
修饰,且方法名以Async结尾。异步方法体至少包含一个await
表达式。await
能够看做是一个挂起异步方法的一个点,且同时把控制权返回给调用者。异步方法的返回值必须是Task
或者Task<T>
。即若是方法没有返回值那就用Task表示,若是有一个string类型的返回值,就用Task泛型Task<string>
修饰。
异步方法执行流程:
Task.Delay
结束,await
表达式结束,MehtodAsync执行await表达式以后的语句,输出“异步执行结束”。和其余方法同样,async方法开始时以同步方式执行。在async内部,await关键字对它的参数执行一个异步等待。它首先检查操做是否已经完成,若是完成了,就继续运行(同步方式)。不然它会暂停async方法,并返回,留下一个未完成的Task。一段时间后,操做完成,async方法就恢复运行。
一个async方法是由多个同步执行的程序块组成的,每一个同步程序块之间由await语句分隔。第一个同步程序块是在调用这个方法的线程中执行,但其余同步程序块在哪里运行呢?状况比较复杂。
最多见的状况是用await语句等待一个任务完成,当该方法在await处暂停时,就能够捕获上下文(context)。若是当前SynchronizationContext不为空,这个上下文就是当前SynchronizationContext。若是为空,则这个上下文为当前TaskScheduler。该方法会在这个上下文中继续运行。通常来讲,运行在UI线程时采用UI上下文,处理Asp.Net请求时采用Asp.Net请求上下文,其余不少状况下则采用线程池上下文。
由于,在上面的代码中,每一个同步程序块会试图在原始的上下文中恢复运行。若是在UI线程调用async方法,该方法的每一个同步程序块都将在此UI线程上运行。可是,若是在线程池中调用,每一个同步程序块将在线程池上运行。
若是要避免这种行为,能够在await中使用configureAwait方法,将参数ContinueOnCapturedContext设置为false。async方法中await以前的代码会在调用的线程里运行。在被await暂停后,await以后的代码则会在线程池里继续运行。
async Task MethodAsync() { Console.WriteLine("异步执行");//同步程序块1 await Task.Delay(4000).ConfigureAwait(false); Console.WriteLine("异步执行结束");//同步程序块2 }
咱们可能想固然的认为Task.Delay
会阻塞执行线程,就跟Thread.Sleep
同样。其实他们是不同的。Task.Delay
建立一个将在设置时间后执行的任务。就至关于一个定时器,多少时间后再执行操做。不会阻塞执行线程。
当咱们在异步线程中调用Sleep的时候,只会阻塞异步线程。不会阻塞到主线程。
async Task Method2Async() { Console.WriteLine("await执行前..."+Thread.CurrentThread.ManagedThreadId); await Task.Run(() => { Console.WriteLine("await执行..." + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(5000); Console.WriteLine("await执行结束..." + Thread.CurrentThread.ManagedThreadId); }); Console.WriteLine("await以后执行..."+ Thread.CurrentThread.ManagedThreadId); } //输出: //await执行前...9 //await执行...12 //await以后执行...9 //await执行结束...12
上面的异步方法,Task
建立了一个线程池线程,Thread.Sleep执行在线程池线程中。
参考: