摘要
异步编程是必备技能,能够同时解决多个关键问题,如I/O堵塞和高并发操作。通过.NET的实体模型,我们可以为应用程序和服务项目提供更加响应和可扩展的解决方案。在日常编程中,异步编程是必不可少的,因为它可以帮助我们更好地处理文档和网络I/O等问题。
正文
1、异步编程
异步编程是一项核心技术,能够同时解决好几个关键上的堵塞 I/O 和高并发实际操作。 根据 C#、Visual Basic 和 F# 中便于应用的语言表达级异步编程实体模型,.NET 能为运用和服务项目给予使其越来越可回应且具有延展性。
上边是有关异步编程的表述,大家日常程序编写全过程多多少少的会应用到异步编程,为何要使用异步编程?由于用程序流程处理中应用文档和互联网 I/O,例如解决材料的载入载入硬盘,互联网要求插口API,默认设置 状况下 I/O API 一般会堵塞。
那样的结论是致使人们的操作界面卡死感受差,有一些网络服务器的硬件配置使用率低,服务项目解决工作能力要求回应慢等难题。根据每日任务的多线程 API 和语言表达级异步编程实体模型更改了这类实体模型,只需掌握好多个新理念就可默认设置 开展多线程实行。
如今广泛采用的异步编程方式是TAP方式,也就是C# 给予的 async 和 await 关键字,事实上大家也有此外二种多线程方式:根据事情的多线程方式 (EAP),及其异步编程实体模型 (APM) 。
APM 是根据 IAsyncResult 插口给予的异步编程,比如像FileStream类的BeginRead,EndRead便是APM完成方法,给予一对逐渐完毕方式 用于运行和接纳多线程結果。应用授权委托的BeginInvoke和EndInvoke的方法来完成异步编程。
EAP 是在 .NET Framework 2.0 中导入的,比较多的表现在WinForm程序编写中,WinForm程序编写中许多控制事件处理全是根据事情实体模型,常常使用跨进程升级页面的情况下便会运用到BeginInvoke和Invoke。事情方式算得上对APM的一种填补,界定了一系列事情包含进行、进展、撤销的事情使我们在异步调用的情况下能申请注册回应的事情开展实际操作。
class Program
{
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now " start");
IAsyncResult result = BeginAPM();
//EndAPM(result);
Console.WriteLine(DateTime.Now " end");
Console.ReadKey();
}
delegate void DelegateAPM();
static DelegateAPM delegateAPM = new DelegateAPM(DelegateAPMFun);
public static IAsyncResult BeginAPM()
{
return delegateAPM.BeginInvoke(null, null);
}
public static void EndAPM(IAsyncResult result)
{
delegateAPM.EndInvoke(result);
}
public static void DelegateAPMFun()
{
Console.WriteLine("DelegateAPMFun...start");
Thread.Sleep(5000);
Console.WriteLine("DelegateAPMFun...end");
}
}
如上编码我应用授权委托完成异步调用,BeginAPM 方式 应用 BeginInvoke 逐渐异步调用,随后 DelegateAPMFun 多线程方式 里边停5秒。看下接下来的打印出結果,是 main 方式 里边的打印出在前,多线程方式 里边的打印出后面,表明该使用是串行的。
在其中一行编码EndAPM(result)
被注解了,启用了授权委托 EndInvoke 方式 ,该办法会堵塞程序流程直至异步调用进行,因此 我们可以放进适度的部位用于获得实行結果,这类似TAP方式的await 关键词,放宽转行程序执行下。
之上这两种方法已不建议应用,撰写了解起來非常晦涩难懂,有兴趣的能够自己掌握下,并且这个形式在.net 5里边早已不兼容授权委托的异步调用了,因此假如要运作必须在.net framework架构下。
TAP 是在 .NET Framework 4 中导入的,是现在建议的多线程策略模式,也是大家文中探讨的主要方位,可是TAP并不一定是进程,他是一种每日任务,了解为工作中的多线程抽象性,并非在进程以上的抽象性。
2、async await
应用 async await 关键词能够 很简单的完成异步编程,大家子必须将方式 再加上 async 关键词,方式 内的多线程实际操作应用 await 等候多线程实际操作结束后再运行事后实际操作。
class Program
{
static void Main(string[] args)
{
Console.WriteLine(DateTime.Now " start");
AsyncAwaitTest();
Console.WriteLine(DateTime.Now " end");
Console.ReadKey();
}
public static async void AsyncAwaitTest()
{
Console.WriteLine("test start");
await Task.Delay(5000);
Console.WriteLine("test end");
}
}
AsyncAwaitTest 方式 应用 async 关键词,应用await关键词等候5秒后打印出”test end”。在 Main 方式 里边启用 AsyncAwaitTest 方式 。
应用 await 在任务完成前将操纵妥协于其启用方,可让程序和服务项目实行有效工作中。 任务完成后编码不用借助回调函数或事情便可执行。 语言表达和每日任务 API 集成化会给你进行此实际操作。
应用await 的办法需要应用 async 关键词,如果我们 Main 方式 里边想等候 AsyncAwaitTest 则 Main 方式 必须再加上 async 并回到 Task。
3、async await 基本原理
将上边 Main 方式 不应用 await 启用的形式编译程序后应用ILSpy反汇编dll,应用C# 4.0才可以见到c语言编译器为大家做过哪些。由于4.0不兼容 async await 因此 会反汇编到实际编码,4.0 之后的反汇编后会立即表明 async await 英语的语法。
根据反汇编后还可以看见在多线程方式 里边再次转化成了一个泛型类 d._1 完成插口IAsyncStateMachine,随后启用Start方式 ,Start中完成了一些进程解决后启用 stateMachine.MoveNext()
即启用d._1创建对象目标的MoveNext方式 。
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
Thread currentThread = Thread.CurrentThread;
Thread thread = currentThread;
ExecutionContext executionContext = currentThread._executionContext;
ExecutionContext executionContext2 = executionContext;
SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
{
stateMachine.MoveNext();
}
finally
{
SynchronizationContext synchronizationContext2 = synchronizationContext;
Thread thread2 = thread;
if (synchronizationContext2 != thread2._synchronizationContext)
{
thread2._synchronizationContext = synchronizationContext2;
}
ExecutionContext executionContext3 = executionContext2;
ExecutionContext executionContext4 = thread2._executionContext;
if (executionContext3 != executionContext4)
{
ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
}
}
}
大家再看c语言编译器为转化成的类 <AsyncAwaitTest>d._1
:
MoveNext方式 将 AsyncAwaitTest 逻辑性编码包括进去,大家的源码由于只有一个 await 实际操作,如果有好几个 await 实际操作,那麼MoveNext里边应当还会继续有好几个按段逻辑性,将不一样段的MoveNext放进差异的情况按段块。
在此类中也有一个if分辨,依照 1._state 状态参数,最初启用的那时候是-1,实行进去 num != 0
则实行大家的项目编码if里边的,这个时候会次序实行业务流程编码,直至遇到 await 则实行以下编码
awaiter = Task.Delay(5000).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<> 1._state = 0);
<> u._1 = awaiter;
< AsyncAwaitTest > d._1 stateMachine = this;
<> t._builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
在该操作过程中 <> t._builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
将 await 句和状态机开展传送启用 AwaitUnsafeOnCompleted
方式 ,该方式 一直跟下来会寻找线程池的实际操作。
// System.Threading.ThreadPool
internal static void UnsafeQueueUserWorkItemInternal(object callBack, bool preferLocal)
{
s_workQueue.Enqueue(callBack, !preferLocal);
}
程序流程将封裝的每日任务放进线程池开展启用,这个时候多线程方式 就转换到另一个进程,或是在原进程上实行(假如多线程方式 实行時间非常短很有可能就不易开展进程转换,这一具体看生产调度程序流程)。
实行进行 await 后情况 1._state 早已变更了为 0,程序流程会再度启用 MoveNext 进到 else 以后沒有return和其他逻辑性,则执行到完毕。
能够 见到这是一个情况操纵的实行逻辑性,是一种“状态机方式”的策略模式,针对 Main 方式 启用 AsyncAwaitTest 逻辑性此时进到if,遇到await则进到进程生产调度实行,假如多线程方式 转换到其他进程启用,则方式 Main 执行,当状态机实行转换到此外一个情况后再度 MoveNext 直至实行完多线程方式 。
4、async 与 进程
拥有里面的基本我们知道 async 与 await 一般 是成对相互配合应用的,在我们的方式 标识为多线程的情况下,里边的用时实际操作就必须 await 开展标识等候进行后实行事后逻辑性,启用该多线程方式 的入参能够 选择是不是等候,假如无需 await 则入参多线程实行或是就在原进程上实行多线程方式 。
假如 async 关键词改动的方式 不包含 await 关系式或句子,则该办法将同歩实行,替代性根据 Task.Run API 显式要求每日任务在单独进程上运作。
能够 将 AsyncAwaitTest 方式 改成表明进程运作:
public static async Task AsyncAwaitTest()
{
Console.WriteLine("test start");
await Task.Run(() =>
{
Thread.Sleep(5000);
});
Console.WriteLine("test end");
}
5、撤销每日任务 CancellationToken
假如不希望等候多线程方式 进行,能够根据 CancellationToken 撤销该每日任务,CancellationToken 是一个struct,一般 应用 CancellationTokenSource 来建立 CancellationToken,由于CancellationTokenSource 有一些列的[方式 ]用以大家撤销每日任务而无需去实际操作CancellationToken 建筑结构。
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
然我更新改造下方式 ,将 CancellationToken 传送到多线程方式 ,cts.CancelAfter(3000)
3秒左右后撤销每日任务,大家监视CancellationToken 假如 IsCancellationRequested==true
则立即回到 。
static void Main(string[] args)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
cts.CancelAfter(3000);
Console.WriteLine(DateTime.Now " start");
AsyncAwaitTest(ct);
Console.WriteLine(DateTime.Now " end");
Console.ReadKey();
}
public static async Task AsyncAwaitTest(CancellationToken ct)
{
Console.WriteLine("test start");
await Task.Delay(5000);
Console.WriteLine(DateTime.Now " cancel");
if (ct.IsCancellationRequested) {
return;
}
//ct.ThrowIfCancellationRequested();
Console.WriteLine("test end");
}
由于我们都是手动式根据编码分辨情况完毕多线程,因此 即便 在3秒后就己经告一段落每日任务,可是await Task.Delay(5000)
依然会等候5秒实行完。也有一种方法便是我们不分辨是不是撤销,立即启用ct.ThrowIfCancellationRequested()
给大家分辨,这一办法假如,可是依然不可以立即完毕。这个时候大家也有此外一种处理方法,便是将CancellationToken 传送到 await 的多线程API方式 里,很有可能会马上完毕,也有可能不容易,这一要取决于多线程完成。
public static async Task AsyncAwaitTest(CancellationToken ct)
{
Console.WriteLine("test start");
//传送CancellationToken 撤销
await Task.Delay(5000,ct);
Console.WriteLine(DateTime.Now " cancel");
//手动式解决撤销
//if (ct.IsCancellationRequested) {
// return;
//}
//启用方式 解决撤销
//ct.ThrowIfCancellationRequested();
Console.WriteLine("test end");
}
6、留意项
在多线程方式 里边不能应用 Thread.Sleep 方式 ,有二种很有可能:
1、Sleep在 await 以前,则会立即堵塞启用方进程等候Sleep。
2、Sleep在 await 以后,可是 await 实行在启用方的进程上也会堵塞启用方进程。
因此 大家应当应用 Task.Delay 用以等候实际操作。那为啥上边的 Task.Run 里边运用了 Thread.Sleep呢,由于 Task.Run 是表明要求在单独进程上运作,因此 我明白这儿写不容易堵塞启用方,上边我只是为了更好地演试,因此 不建议用。
创作者:SunSpring
来源:https://www.cnblogs.com/SunSpring/p/15166143.html
文中著作权归原作者全部,热烈欢迎转截,但没经创作者允许需要在文章内容网页页面显著部位得出全文连接。
关注不迷路
扫码下方二维码,关注宇凡盒子公众号,免费获取最新技术内幕!
评论0