异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序
本节导读:
本节主要说明使用异步进行程序设计的优缺点及如何通过异步编程.
使用传统方法BeginInvoke / EndInvoke来实现异步。
使用async/await 创建异步方法和事件。
通过异步编程创建具有快速响应和可伸缩性的应用程序。
读前必备:
A.委托 [.net 面向对象编程基础] (21) 委托
B.事件 [.net 面向对象编程基础] (22) 事件
1.异步程序设计的优缺点:
A.让用户界面快速响应;对于耗时操作阻塞UI线程,通过异步回调可使用UI快速响应。
B.创建高伸缩性的应用。对于服务端应用,创建更多线程来处理消耗资源较多,使用异步可使用主线程继续工作,不需要等待返回。使用程序具有更好的伸缩性。
对于异步在代码调试中跟并发一样,执行时需要对cpu资源进行抢夺,从而跳来跳去
线程同步就是多个线程一个一个来,直到一个线程执行完 ,才能开始下一个线程;线程异步就是两个线程可以同时执行,不需要一个线程执行完另一个线程才可以执行
对于异步的缺点,最主要一点就是比创建同步程序难度大一些,首先使用传统的方法创建异步,比起同步更容易出错。不过随着.NET的不断发展和第三方异步组件的丰富,创建异步应用程序也变得越来越简单了。
2.异步的实现
对于.NET中的异步编程,.NET在各个方向都几乎提供了同步和异步的两个方式来实现,在这里我们不能把.NET中全部的异步编程方法都列举出来了,下面介绍几种常用且实用的异步方法。
3.使用BeginInvoke / EndInvoke实现异步
3.1 简单的异步示例
using System;
using System.Threading;
namespace Test2
{
class Program
{
static void Main(string[] args)
{
Func<string> func = new Func<string>(()=>
{
Thread.Sleep(100);
return "我是异步执行完成的返回值:" + DateTime.Now.ToString();
});
IAsyncResult asyncResult = func.BeginInvoke(null, null);
while (!asyncResult.IsCompleted)
{
Console.WriteLine("异步调用还未完成,主线程执行中...");
}
string result = func.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.ReadKey();
}
}
}
运行结果如下:
在异步没有完成时,可以继续工作做一些想做的事,异步完成后返回结果。
3.2 使用异步的超时 WaitOne 判断异步完成
除了上面使用IsCompleted来判断异步完成之外,也可以使用WaitOne 来判断异步的完成情况
示例如下 :
Func<string> myFunc = new Func<string>(()=>{
int i = 0;
while (i<99999999)
++i;
return "异步执行完成的返回值" + (i).ToString() + " 当前时间:" + System.DateTime.Now.ToString();
});
IAsyncResult asynResult = myFunc.BeginInvoke(null, null);
while (!asynResult.AsyncWaitHandle.WaitOne(10, false))
Console.Write("*");
string result = myFunc.EndInvoke(asynResult);
Console.Write("\n");
Console.WriteLine(result);
运行结果如下:
下面的代码是为了理解mre.WaitOne(100000, false)中退出与不退出同步域的区别
using System;
using System.Diagnostics;
using System.Runtime.Remoting.Contexts;
using System.Threading;
using System.Threading.Tasks;
namespace MultithreadingApplication
{
class Program
{
static void Main(string[] args)
{
BaseClass firstClass = new BaseClass();
ChildClass secondClass = firstClass.GetChildClass();
Thread firstThread = new Thread(firstClass.method);
Thread secondThread = new Thread(secondClass.method);
firstThread.Start();
Thread.Sleep(3000);
secondThread.Start();
Console.Read();
}
}
[Synchronization(true)]
class BaseClass : ContextBoundObject
{
public static ManualResetEvent mre = new ManualResetEvent(false);
public static Stopwatch stopwatch = new Stopwatch();
public virtual void method()
{
stopwatch.Start();
Console.WriteLine("BaseClass method begin");
mre.WaitOne(100000, false);
Console.WriteLine("BaseClass method end after {0} ms", stopwatch.ElapsedMilliseconds);
stopwatch.Stop();
}
public ChildClass GetChildClass()
{
return new ChildClass();
}
}
class ChildClass : BaseClass
{
public override void method()
{
Console.WriteLine("ChildClass1 method entered synchromization domain after {0} ms", stopwatch.ElapsedMilliseconds);
}
}
}
3.3 回调
毕竟上述两种等待不是一个好的方法。我们在前端开发中使用过ajax的同学肯定知道, 前端中异步使用一个回调函数,在异步完成后完成我们想要做的事,.NET自然也有类似的回调方法,看示例:
Func<string> myFunc = new Func<string>(() => {
int i = 0;
while (i < 9)
++i;
return "异步执行完成的返回值" + (i).ToString() + " 当前时间:" + System.DateTime.Now.ToString();
});
IAsyncResult asynResult = myFunc.BeginInvoke((result) =>
{
string rst = myFunc.EndInvoke(result);
Console.WriteLine("异步完成了,我该返回结果了!");
Console.WriteLine(rst);
}, null);
Console.ReadKey();
运行结果如下 :
下面代码是自己写的带参数返回值的异步委托,回调时的情况
using System;
namespace MultithreadingApplication
{
delegate string MyDelegate(string s);
class Program
{
public string M(string s)
{
return s;
}
static void Main(string[] args)
{
Program program = new Program();
MyDelegate myDelegate = new MyDelegate(program.M);
IAsyncResult asyncResult = myDelegate.BeginInvoke("sssss",(ia) =>
{
string rs = myDelegate.EndInvoke(ia);
Console.WriteLine("异步执行完成,返回结果");
Console.WriteLine(rs);
},null);
Console.ReadKey();
}
}
}
3.4 其它组件中的Begin\End异步方法
除了BeginInvoke / EndInvoke之外,.NET在很多类中提供了异步的方法,
如System.Net.HttpWebRequest类的BeginGetResponse和EndGetResponse方法,
这里不再一一列举了,使用方法和上面的示例类似。
4. async/await
.NET 5.0 以后,让异步编程变得更加简单了,我们介绍一下async和await。
它让我们编写异步程序变得和同步一样简单,不但减少了代码量,而且不会因为异步让我们程序逻辑被打乱。
4.1 异步方法
下面使用async 和 await关键字来创建一个异步方法,
在第一个方法里调用第二个异步方法,
第二个异步方法中使用了多线程。
听起来很绕口,不过整个代码编写和同步方法没有什么区别,只是多一个关键字。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MultithreadingApplication
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("主线程开始..");
AsyncMethod();
Thread.Sleep(1000);
Console.WriteLine("主线程结束..");
Console.ReadKey();
}
static async void AsyncMethod()
{
Console.WriteLine("开始异步方法");
var result = await MyMethod();
Console.WriteLine("异步方法结束");
}
static async Task<int> MyMethod()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("异步执行" + i.ToString() + "...");
await Task.Delay(1000);
}
return 0;
}
}
}
运行结果如下:
5. 本节要点
A.使用传统方法BeginInvoke / EndInvoke来实现异步