威尼斯人线上娱乐

异步编制程序,不得不说的异步编制程序

6 4月 , 2019  

一、什么是异步编制程序?

只可以说的异步编制程序,不得不说异步编制程序

一、什么是异步编制程序?

   
异步编制程序正是把耗费时间的操作放进3个独自的线程中开始展览处理(该线程须要将推行进度反映到界面上)。由于耗费时间操作是在别的1个线程中被实施的,所以它不会杜绝主线程。主线程开启那一个单独的线程后,还足以继续执行其余操作(例如窗体绘制等)。

   
异步编制程序能够增进用户体验,制止在进行耗费时间操作时让用户看到程序“卡死”的处境。

 

2、异步编制程序模型(APM)

    APM是Asynchronous Programming
Mode的缩写,即异步编制程序模型的趣味,它同意程序用更加少的线程去实践越来越多的操作。在.NET
Framework中,要辨识某些类是不是贯彻了异步编制程序模型,首要便是看该类是不是落实了回来类型为IAsyncResult接口的BeginXXX方法和EndXXX方法。

   
由于委托类型定义了BeginInvoke和EndInvoke方法,所以委托项目都落实了异步编制程序模型。

    贰.一 Beginxxx方法–开首履行异步操作

         
在急需获得文件中的内容时,我们普通会使用FileStream的同步方法Read举办读取,该联合方法的概念为:

          public override int Read(byte[] array,int offset,int count)

         
当使用方面包车型地铁艺术读取大文件的剧情时,会出现堵塞UI线程,导致在文书内容尚未读取实现此前,用户无法对窗体进行别的操作(包涵关闭应用程序),那时窗体就会并发无法响应的动静。

          为了消除那些标题,微软早在.NET
1.0的时候就建议了异步编制程序模型,并为FileStream类提供了异步方式的不二等秘书诀达成,即BeginRead方法。该方法会异步地推行读取操作,并回到实现了IAsyncResult接口的靶子(该对象存款和储蓄这异步操作的消息)。

         
上边给出了BeginRead方法的概念,我们能够从中找出它与共同方法Read的分歧:

          public override IAsyncResult BeginRead(byte[] array,int
offset,int numBytes,AsyncCallback userCallback,Object stateObject)

         
从以上的异步方法的概念能够观察,该异步方法的前边一个参数与1同方法Read壹致,后多个参数userCallback和StateObject则是同步方法所不负有的。userCallback表示异步操作完毕后供给回调的艺术,该办法必须相称AsyncCallback委托项目;stateObject则意味传递给回调方法的靶子,在回调方法中,能够由此查询IAsyncResult接口的AsyncState属性来读取该对象。该异步方法之所以不会堵塞UI线程,是因为它在被调用后,会马上把控制权交还给调用线程(倘诺是UI线程调用了该格局,则就将控制权重回给UI线程),然后由另三个线程去履行文书读取操作。

 

    二.二 Endxxx方法–结束异步操作

         
每一次调用Beginxxx方法后,应用程序还需调用Endxxx方法来得到操作重返的结果。Beginxxx方法所重返的,是促成了IAsyncResult接口的靶子,该目的无须相应的一起方法重回的结果。此时还索要调用Endxxx方法来终止异步操作,并向该方法传递Beginxxx所重临的靶子。Endxxx方法重返的连串与联合方法一致,如FileStream的EndRead方法会再次回到一个Int32品种,代表从文件流中实际读取的字节数。

         
Endxxx方法有许多中艺术调用,但有1种是最常用的,即利用AsyncCallback委托来钦赐操作达成时要调用的不二等秘书诀,在回调方法中调用Endxxx方法来赢得异步操作再次来到的结果。

 1 static void Main()
 2 {
 3     SynchronizationContext sc=SynchronizationContext.Current;
 4     AsyncMethodCaller methodCaller=new AsyncMethodCaller(DownLoadFileAsync);
 5     method.BeginInvoke(txtUrl.Text.Trim(),GetResult,null);
 6 }
 7 private static void GetRsult(IAsyncResult result)
 8 {
 9     AsyncMethodCaller caller=(AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;
10     string returnstring=call.EndInvoke(result);
11 }

 

异步编制程序,不得不说的异步编制程序。三、异步编制程序模型(EAP)

   
固然近来的异步编程能够缓解执行耗费时间操作时界面不可能响应的题材,但APM也1样存在这一部分综上说述的标题,如不支持对异步操作的吊销以及不能够提供下载速度报告等。可是对于桌面应用程序而言,进程报告和撤回操作的作用是不能缺少的,所以微软在.NET
2.0
公布时又建议了三个新的异步编制程序模型–基于事件的异步模型,即EAP(伊夫nt-based
Asynchronous Pattern)。

   
完结了EAP的类具有三个或八个以Async为后缀的法子,以及相应的Completed事件,并且那么些类支持异步方法的撤除和速度报告。在.NET类库中,唯有部分类实现了EAP,共1多少个。在那壹8个类中,开发进度中运用最多的其实BackgroundWorker类了。

    日常使用的习性为:

    CancellationPending:用来提醒应用程序是还是不是已呼吁裁撤后台操作;

    IsBusy:提示异步操作是不是正在运作;

    WorkReportProgress:只是BackgrounWorker能不可能报告进程;

   
WorkerSupportsCancellation:提醒BackgroundWoker是不是援助异步打消操作;

    平时应用的方法为:

    CancelAsync:请求撤除异步操作;

    ReportProgress:用于引发ProgressChanged事件;

    RunWorkAsync:调用后伊始举行异步操作;

    平常利用到的一个事件为:

    DoWork:调用RunWokerAsync时接触的轩然大波;

   
ProgressChanged:调用ReportProgress时接触的事件,程序会在该事件中进行速度报告的创新;

    RunWorkerCompleted:当异步操作已到位、被撤消或引发这个时被触发。

    那种艺术已经很少用到了,所以那边就不详细介绍了。

 

4、TAP又是如何?

    前面介绍了.NET提供的三种异步编制程序格局,分别为.NET 1.0中的APM和.NET
2.0中的EAP。纵然这二种异步编制程序情势能够落成多数境况下的异步编程,可是它们在MSDN文书档案上都被标明为了不引入使用的贯彻方式,因为在.NET
4.0中,微软又提供了更简约的异步编制程序完毕格局–TAP,基于职分的异步格局。

   
该形式首要利用System.Threading.Tasks命名空间中的Task<T>类来完结异步编制程序,所以在利用TAP在此之前,首先要引进System.Threading.Tasks命名空间。

    基于职责的异步形式(TAP,Task-based Asynchronous
Pattern)只行使叁个方式就能表示异步操作的起来和实现,而APM却须要Beginxxx和Endxxx三个主意分别表示开头和了结,EAP则要求具有以Async为后缀的点子和2个或四个事件。在遵照职责的异步格局中,只需求三个以TaskAsync为后缀的法门,通过向该措施传入CancellationToken参数,大家就能够很好地做到异步编制程序了。而且,还足以通过IProgress<T>接口来贯彻速度报告的作用。总体来说,使用TAP会减少大家的工作量,是代码特别简明。

1 Task task=new Task(()=>{.......});
2 task.Start();

 

五、让异步编制程序So easy——C# 5.0中的async和await

    即便.NET 1.0和.NET 二.0和.NET
四.0都对异步编制程序做了很好的支撑,微软也稳步地利用异步编制程序变得简单,但微软认为现有的干活还不够,它愿意使异步编制程序的支付进度特别简化,所以在.NET
四.5中,微软又建议了async和await五个重要字来帮助异步编制程序。

    这也是如今.NET
Framework中最简单易行的异步编制程序完毕情势,因为使用那么些四个关键字展开异步编制程序,思索格局和贯彻同步编制程序时的一点一滴等同。

   
async和await关键字不会让调用方法运行在新线程中,而是将艺术分割成多个部分(片段的界限出现在章程内部采取await关键字的地方处),并使内部有的局地能够异步运营。await关键字处的代码片段是在线程池线程上运转的,而任何艺术的调用确实联合的。所以,使用此情势编制程序不用思考跨线程访问UI控件的题材,从而大大降低了异步编制程序的出错率。

一、什么是异步编制程序?
异步编制程序就是把耗费时间的操作放进三个单身的线程中开始展览处理(该线程要求将…

①、什么是异步编制程序?

 IO操作的MDA(Direct memory
access)格局:直接待上访问内部存款和储蓄器,是一种不通过CPU而直白进行内部存款和储蓄器数据存款和储蓄的数据交流格局,大概能够不损耗CPU的财富;
 CLRAV四所提供的异步编制程序模型就是足够利用硬件的DMA效用来刑释CPU的下压力;使用线程池进行管制,异步将工作移交给线程池中的有些工作线程来形成,直到异步完结,异步才会通过回调的措施通告线程池,让CL奥迪Q5响应异步完成;

   
异步编制程序便是把耗费时间的操作放进三个独门的线程中展开始拍录卖(该线程需求将实施进程反映到界面上)。由于耗费时间操作是在此外一个线程中被实施的,所以它不会堵塞主线程。主线程开启这一个单独的线程后,还是能继续执行别的操作(例如窗体绘制等)。

   
异步编制程序正是把耗费时间的操作放进1个独自的线程中开始展览拍卖(该线程必要将推行进程反映到界面上)。由于耗费时间操作是在别的贰个线程中被实施的,所以它不会堵塞主线程。主线程开启那一个单独的线程后,还是能继续执行其余操作(例如窗体绘制等)。

它是出现的壹种情势,它利用 future
格局或回调(callback)机制,避防止发生不供给的线程。3个 future(或
promise)类型代表有个别快要完毕的操作。在 .NET 中,新版 future 类型有Task
和Task<TResult>。 

   
异步编制程序能够增强用户体验,幸免在进行耗费时间操作时让用户观察程序“卡死”的场馆。

   
异步编制程序能够增强用户体验,制止在实行耗费时间操作时让用户看到程序“卡死”的境况。

异步编制程序情势——行使委托和线程池达成的形式

APM 异步编制程序模型,Asynchronous Programming Model            C#1.0

EAP 基于事件的异步编制程序方式,伊夫nt-based Asynchronous Pattern  C#2.0

TAP 基于职分的异步编制程序格局,Task-based Asynchronous Pattern    C#4.0

Async\await简化异步编制程序;任务并行库,Task Parallel Library     C#5

 

 

APM

         使用IAsyncResult设计格局的异步操作是经过名字为 BeginXXX 和 EndXXX
的多个措施来贯彻,那七个艺术分别指初步和终止异步操作。该方式允许用越来越少的CPU财富(线程)去做更加多的操作,.NET
Framework很多类也兑现了该情势,同时大家也得以自定义类来贯彻该情势(也便是在自定义的类中达成重返类型为IAsyncResult接口的BeginXXX方法和接受IAsyncResult包容类型作为唯1参数的EndXXX方法),别的事委员会托项目也定义了BeginInvoke和EndInvoke方法。例如,FileStream类提供BeginRead和EndRead方法来从文件异步读取字节。那多个章程达成了
Read 方法的异步版本。

调用 BeginXXX
后,应用程序能够继承在调用线程上实施命令,同时异步操作在另三个线程上进行(要是有重回值还应调用
EndXXX终止异步操作,并向该方式传递BeginXXX
方法重返的IAsyncResult对象,赢得操作的返回值)。

 威尼斯人线上娱乐 1

CompletedSynchronously属性值侧重与提醒音信,而非操作

走访异步操作的结果,APM提供了八种方法:

1.在调用BeginXXX方法的线程上调用EndXXX方法来收获异步操作的结果;但是那种方法会卡住调用线程,在知情操作完结之后调用线程才能三番五次运营。

二.循环查询IAsyncResult的IsComplete属性,操作达成后再调用EndXXX方法来收获操作重临的结果。

3.IAsyncResult的AsyncWaitHandle属性达成更为灵敏的等待逻辑,调用该属性WaitOne()方法来使三个线程阻塞并等候操作实现;再调用EndXXX方法来收获操作的结果。WaitHandle.WaitOne()能够钦点最长的等候时间,如超时再次回到false;

4.
在调用BeginXXX方法时提供AsyncCallback委托的实例作为参数,在异步操作落成后委托会自动调用(AsyncCallback对象)钦定的不二等秘书籍。(首要接纳办法)AsyncCallback委托仅能够调用符合一定方式的主意(唯有三个参数IAsyncResult,且并未有再次来到值);

威尼斯人线上娱乐 2威尼斯人线上娱乐 3

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.Remoting.Messaging;

namespace AsyncCallbackDelegate
{
    public delegate int BinaryOp(int x, int y);
    class Program
    {
        private static bool isDone = false;
        static void Main(string[] args)
        {
            Console.WriteLine("*****  AsyncCallbackDelegate Example *****");
            Console.WriteLine("Main() invoked on thread {0}.",
              Thread.CurrentThread.ManagedThreadId);
            BinaryOp b = new BinaryOp(Add);
            IAsyncResult iftAR = b.BeginInvoke(10, 10,
              new AsyncCallback(AddComplete),
              "Main() thanks you for adding these numbers.");//传入数据
            // Assume other work is performed here...
            while (!isDone)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Working....");
            }
            Console.ReadLine();
        }

        #region Target for AsyncCallback delegate
        // Don't forget to add a 'using' directive for 
        // System.Runtime.Remoting.Messaging!
        static void AddComplete(IAsyncResult itfAR)
        {
            Console.WriteLine("AddComplete() invoked on thread {0}.",
              Thread.CurrentThread.ManagedThreadId);
            Console.WriteLine("Your addition is complete");

            // Now get the result.
            //AsyncCallback委托的目标无法调用其他方法中创建的委托
            //IAsyncResult itfAR 实际上是System.Runtime.Remoting.Messaging命名空间AsyncResult类的一个实例
            AsyncResult ar = (AsyncResult)itfAR;
            //AsyncDelegate静态属性返回原始异步委托引用
            BinaryOp b = (BinaryOp)ar.AsyncDelegate;
            Console.WriteLine("10 + 10 is {0}.", b.EndInvoke(itfAR));

            // Retrieve the informational object and cast it to string.
            //AsyncState属性获取 BeginInvoke第四个参数传入的值
            string msg = (string)itfAR.AsyncState;
            Console.WriteLine(msg);
            isDone = true;
        }

        #endregion

        #region Target for BinaryOp delegate
        static int Add(int x, int y)
        {
            Console.WriteLine("Add() invoked on thread {0}.",
              Thread.CurrentThread.ManagedThreadId);
            Thread.Sleep(5000);
            return x + y;
        }
        #endregion
    }
}

AsyncCallback

可怜捕获

在一起实施的方法里面普通处理十一分的法子是将恐怕抛出十三分的代码放到try…catch…finally里面,之所以能够捕获到,是因为发生卓殊的代码与调用的代码位于同一个线程。当调用一个异步方法产生相当时,CLTiggo会捕获并且在EndXXX方法时再也将格外抛出抛出,所以异步调用中的非常在EndXXX方法出捕获就行了。

class ApmExceptionHandling 
{
   public static void Go() 
  {
      WebRequest webRequest = WebRequest.Create("http://0.0.0.0/");
      webRequest.BeginGetResponse(ProcessWebResponse, webRequest);
      Console.ReadLine();
   }
   private static void ProcessWebResponse(IAsyncResult result) {
      WebRequest webRequest = (WebRequest)result.AsyncState;

      WebResponse webResponse = null;
      try {
         webResponse = webRequest.EndGetResponse(result);
         Console.WriteLine("Content length: " + webResponse.ContentLength);
      }
      catch (WebException we) {
         Console.WriteLine(we.GetType() + ": " + we.Message);
      }
      finally {
         if (webResponse != null) webResponse.Close();
      }
   }
}

APM WinForm UI线程回调

出于AsyncCallback委托回调是从ThreadPool中的线程执行的,由此对此Winform,若是回调须要操作UI控件,就供给回到到UI线程去,常用的四个点子:

一. 
Control类达成了ISynchronizeInvoke接口,提供了Invoke和BeginInvoke方法来协理其余线程更新GUI界面控件的编写制定(将回调方法投递到开创该控件的线程中实践)。

 威尼斯人线上娱乐 4

Control类的 Invoke,BeginInvoke 内部贯彻如下:

a)
Invoke(同步调用)先判断控件创制线程与近年来线程是还是不是同样,相同则一直调用委托方法;不然使用Win3二API的PostMessage异步执行,然而Invoke内部会调用IAsyncResult.AsyncWaitHandle等待执行到位。

b) BeginInvoke(异步调用)使用Win32API的PostMessage 异步执行,并且再次来到IAsyncResult 对象。

动用办法:回调方法中对控件检查评定InvokeRequired值,if
true,在该回调中封送三次委托,调用控件的Invoke/ BeginInvoke方法;

 威尼斯人线上娱乐 5

二.GUI(WinForm/WPF)应用程序引进了1个线程处理模型:创建窗口的线程是绝无仅有能对充足窗口进行翻新的线程;在GUI线程中,经常索要转移异步操作,使GUI线程不打断并甘休响应用户输入。但是,异步操作完结时,由于是用2个线程池线程完毕的,而线程池线程不能够更新UI控件。为缓解那一个题材,FCL定义三个System.Threading.SynchronizationContext(线程同步上下文)的基类,其派生对象承担将一个应用程序模型连接到它的线程处理模型。

GUI线程都有1个和它事关的SynchronizationContext派生对象,使用其静态Current属性获取:SynchronizationContext
sc = SynchronizationContext.Current;
将此指标传给其余线程,当贰个线程池线程需求让GUI线程更新UI时,调用该目的的sc.Post方法,向Post传递三个匹配SendOrPostCallback委托签名的回调方法(一般是更新UI的操作方法,由GUI线程去实践),以及一个要传给回调方法的实参。

SynchronizationContext
的Post方法和Send方法的界别:(分别对应于异步/同步调用)

Post方法将回调方法送人GUI线程的队列,允许程序池线程立即返回,不进行阻塞;Post方法内部调用了BeginInvoke方法;

Send方法也将回调方法送人GUI线程的队列,但随后就会阻塞线程池线程,直到GUI线程完成对回调方法的调用。阻塞线程池线程极有可能造成线程池创建一个新的线程,避免调用该方法;Send方法内部调用了Invoke方法; 

对winform来说是
System.Windows.Forms.WindowsFormsSynchronizationContext是其子类.

Winform窗口冒出后,UI线程
SynchronizationContext.Current会被绑定赋值,唯有UI线程的Current不为null。

Public class SendOrPostUI {
   public static void Go() {
      System.Windows.Forms.Application.Run(new MyWindowsForm());
   }
   private static AsyncCallback SyncContextCallback(AsyncCallback callback) {
      // Capture the calling thread's SynchronizationContext-derived object
      SynchronizationContext sc = SynchronizationContext.Current;
      // If there is no SC, just return what was passed in
      if (sc == null) return callback;
      // Return a delegate that, when invoked, posts to the captured SC a method that 
      // calls the original AsyncCallback passing it the IAsyncResult argument
      return asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
   }
   private sealed class MyWindowsForm : System.Windows.Forms.Form {
      public MyWindowsForm() {
         Text = "Click in the window to start a Web request";
         Width = 400; Height = 100;
      }
      protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
         // The GUI thread initiates the asynchronous Web request 
         Text = "Web request initiated";
         var webRequest = WebRequest.Create("http://Wintellect.com/");
         webRequest.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webRequest);
         base.OnMouseClick(e);
      }
      private void ProcessWebResponse(IAsyncResult result) {
         // If we get here, this must be the GUI thread, it's OK to update the UI
         var webRequest = (WebRequest)result.AsyncState;
         using (var webResponse = webRequest.EndGetResponse(result)) {
            Text = "Content length: " + webResponse.ContentLength;
         }
      }
   }
}

正如三种艺术其实差不太多,1个是回调内重新卷入,一个是包装原来的回调。可是SynchronizationContext业务层与UI分离来讲的话是相比好;

2、异步编制程序模型(APM)

二、异步编制程序模型(APM)

EAP

EAP是为了更便于处理UI的更新推出的模式,主要优点:它同Visual Studio UI设计器进行了很好的集成,可将大多数实现了EAP的类拖放到设计平面(design surface)上,双击控件对应的XXXCompleted事件名,会自动生成事件的回调方法,并将方法同事件自身联系起来。EAP保证事件在应用程序的GUI线程上引发,允许事件回调方法中的代码更新UI控件;

EAP另一重要功能:支持EAP的类自动将应用程序模型映射到它的线程处理模型;EAP类在内部使用SynchronizationContext类。有的EAP类提供了取消、进度报告功能。

   FCL中只有十几个体系实现了EAP形式,一般有一个XXXAsync方法和一个对应的XXXCompleted事件,以及这一个点子的同台版本:

*       System.Object的派生类型:*

*                  System.Activies.WorkflowInvoke  *

*                 
System.Deployment.Application.ApplicationDeployment*

*                  System.Deployment.Application.InPlaceHosingManager*

*                  System.Net.Mail.SmtpClient*

*                  System.Net.PeerToPeer.PeerNameResolver*

*                  System.Net.PeerToPeer.Collaboration.ContactManager*

*                  System.Net.PeerToPeer.Collaboration.Peer*

*                  System.Net.PeerToPeer.Collaboration.PeerContact*

*                  System.Net.PeerToPeer.Collaboration.PeerNearMe*

*                 
System.ServiceModel.Activities.WorkflowControlClient*

*                  System.ServiceModel.Discovery.AnnoucementClient*

*                  System.ServiceModel.Discovery.DiscoveryClient*

*      System.ComponentModel.Component的派生类型:*

                 
System.ComponentModel.BackgroundWorker

                 
System.Media.SoundPlay

                 
System.Net.WebClient

                 
System.Net.NetworkInformation.Ping

                 
System.Windows.Forms.PictureBox(继承于Control类,Control类派生于Component类)

private sealed class MyForm : System.Windows.Forms.Form {
    protected override void OnClick(EventArgs e) {
      // The System.Net.WebClient class supports the Event-based Asynchronous Pattern
      WebClient wc = new WebClient();
      // When a string completes downloading, the WebClient object raises the 
      // DownloadStringCompleted event which will invoke our ProcessString method         
      wc.DownloadStringCompleted += ProcessString;
      // Start the asynchronous operation (this is like calling a BeginXxx method)
      wc.DownloadStringAsync(new Uri("http://Wintellect.com"));
      base.OnClick(e);
    }
    // This method is guaranteed to be called via the GUI thread
    private void ProcessString(Object sender, DownloadStringCompletedEventArgs e) {
      // If an error occurred, display it; else display the downloaded string
      System.Windows.Forms.MessageBox.Show((e.Error != null) ? e.Error.Message : e.Result);
      }
   }

BackgroundWorker:唯有该类型用于可用来实施异步的测算范围的干活;提供八个事件:

DoWork:向那么些事件登记的不二诀要应该包罗总结范围的代码。这些事件由三个线程池线程调用RunWorkerAsync(八个重载方法,带参的法子是向DoWork登记的法子的DoWork伊芙ntArgs参数对象的Argument属性传值,只可以在登记的办法中(如e.Argument)获取,Result属性必须设置成总结范围的操作希望重返的值)时引发;

ProgressChanged:向那么些事件登记的不2法门应该包罗使用进程音信来更新UI的代码。那些事件接二连三在GUI线程上引发。DoWork登记的法子必须定期调用BackgroundWorker的ReportProgress方法来诱惑ProgressChanged事件;

RunWorkerCompleted:向这几个事件登记的措施应该包罗使用总结范围操作的结果对UI举办翻新的代码。这几个事件接2连三在GUI线程上引发。Result获取表示异步操作的结果;

国有属性:CancellationPending(标识是不是已呼吁撤废后台操作)、IsBusy(标识是还是不是正在运转异步操作)、WorkReportsProgress(获取/设置是不是报告进程更新)、WorkerSupportsCancellation(获取/设置是不是匡助异步裁撤)

集体艺术:CancelAsync(请求打消挂起的后台操作)、ReportProgress、RunWorkerAsync

异常

很是不会抛出。在XXXCompleted事件处理方法中,必须询问AsyncCompleted伊夫ntArgs的Exception属性,看它是或不是null。如若不是null,就亟须采用if语句判断Exception派生对象的门类,而不是采纳catch块。

    APM是Asynchronous Programming
Mode的缩写,即异步编制程序模型的意味,它同意程序用越来越少的线程去执行更加多的操作。在.NET
Framework中,要甄别有些类是不是贯彻了异步编制程序模型,重要正是看该类是还是不是落实了归来类型为IAsyncResult接口的BeginXXX方法和EndXXX方法。

    APM是Asynchronous Programming
Mode的缩写,即异步编制程序模型的意趣,它同意程序用越来越少的线程去履行越来越多的操作。在.NET
Framework中,要识别有些类是还是不是落到实处了异步编制程序模型,主要就是看该类是或不是完成了回到类型为IAsyncResult接口的BeginXXX方法和EndXXX方法。

TAP

.NET肆.0
中引入了新的异步编制程序模型“基于职务的异步编制程序模型(TAP)”,并且推荐大家在开发新的拾2线程应用程序中首要选取TAP,在.NET四.5中更为对TPL库进行了大气的优化与改进(async和await)。那未来我先介绍下TAP具有什么等优势:

  1. 职责调度器(TaskScheduler)重视于底层的线程池引擎,可自定义1个TaskScheduler更改调度算法,同时不改动代码或编制程序模型。通过壹些队列的职分内联化(task
    inlining)和劳作窃取(work-stealing)机制而发起了大批量职责,Task能够为大家提高程序品质。
  2. 能够运用PreferFairness标志,获取与ThreadPool.QueueUserWorkItem也许三个委托的BeginInvoke相同的线程池行为。

        三.
 轻松达成职分等待、任务废除、一连职分、十分处理(System.AggregateException)、GUI线程操作。

       四.  在职务运转后,可以随时以职责三番五次的样式登记回调。

       五.  足够利用现有的线程,制止创立不须求的额外线程。

       6.  结合C#五.0引进async和await关键字轻松完结“异步方法”。

APM转换为TAP:

使用TaskFactory的FromAsync方法,传递八个实参:BeginXxx方法、EndXxx方法、Object状态、可选的TaskCreationOptions值,再次来到对三个Task对象的引用;

private static void ConvertingApmToTask() {
      // Instead of this:
      WebRequest webRequest = WebRequest.Create("http://Wintellect.com/");
      webRequest.BeginGetResponse(result => {
         WebResponse webResponse = null;
         try {
            webResponse = webRequest.EndGetResponse(result);
            Console.WriteLine("Content length: " + webResponse.ContentLength);
         }
         catch (WebException we) {
            Console.WriteLine("Failed: " + we.GetBaseException().Message);
         }
         finally {
            if (webResponse != null) webResponse.Close();
         }
      }, null);
      Console.ReadLine();  // for testing purposes
      // Make a Task from an async operation that FromAsync starts
      webRequest = WebRequest.Create("http://Wintellect.com/");
      var t1 = Task.Factory.FromAsync<WebResponse>(webRequest.BeginGetResponse, webRequest.EndGetResponse, null, TaskCreationOptions.None);
      var t2 = t1.ContinueWith(task => {
         WebResponse webResponse = null;
         try {
            webResponse = task.Result;
            Console.WriteLine("Content length: " + webResponse.ContentLength);
         }
         catch (AggregateException ae) {
            if (ae.GetBaseException() is WebException)
               Console.WriteLine("Failed: " + ae.GetBaseException().Message);
            else throw;
         }
         finally { if (webResponse != null) webResponse.Close(); }
      });
      try {t2.Wait();  // for testing purposes only}
      catch (AggregateException) { }
   }

EAP转换成TAP:

行使System.Threading.Tasks.TaskCompletionSource类举行打包;

威尼斯人线上娱乐 6

当组织三个TaskCompletionSource对象,也会变卦3个Task,可因而其Task属性获取;当贰个异步操作完结时,它利用TaskCompletionSource对象来安装它因为啥而做到,打消,未处理的老大只怕它的结果。调用有个别SetXxx方法,可以设置底层Task对象的场地。

private sealed class MyFormTask : System.Windows.Forms.Form {
      protected override void OnClick(EventArgs e) {
         // The System.Net.WebClient class supports the Event-based Asynchronous Pattern
         WebClient wc = new WebClient();
         // Create the TaskCompletionSource and its underlying Task object
         var tcs = new TaskCompletionSource<String>();
         // When a string completes downloading, the WebClient object raises the 
         // DownloadStringCompleted event which will invoke our ProcessString method
         wc.DownloadStringCompleted += (sender, ea) => {
            // This code always executes on the GUI thread; set the Task’s state
            if (ea.Cancelled) tcs.SetCanceled();
            else if (ea.Error != null) tcs.SetException(ea.Error);
            else tcs.SetResult(ea.Result);
         };
         // Have the Task continue with this Task that shows the result in a message box
// NOTE: The TaskContinuationOptions.ExecuteSynchronously flag is required to have this code
         // run on the GUI thread; without the flag, the code runs on a thread pool thread 
         tcs.Task.ContinueWith(t => {
            try { System.Windows.Forms.MessageBox.Show(t.Result);}
            catch (AggregateException ae) {
               System.Windows.Forms.MessageBox.Show(ae.GetBaseException().Message);
            }
         }, TaskContinuationOptions.ExecuteSynchronously);
         // Start the asynchronous operation (this is like calling a BeginXxx method)
         wc.DownloadStringAsync(new Uri("http://Wintellect.com"));
         base.OnClick(e);
      }
   }

实现了TAP的类:存在XxxTaskAsync的方法,
扶助异步操作的吊销和过程的报告的职能;

注销:可以透过合作式撤除形式,向异步方法传入CancellationToken 参数,通过调用其ThrowIfCancellationRequested方法来定时检查操作是或不是业已废除;

速度报告:能够通过IProgress<T>接口来完结速度报告的机能;

履新GUI:
TaskScheduler.FromCurrentSynchronizationContext()获取同步上下文职分调度器,将关乎该指标的具有义务都调度给GUI线程,使职务代码能打响更新UI;

private sealed class MyForm : System.Windows.Forms.Form {
        public MyForm() {
            Text = "Synchronization Context Task Scheduler Demo";
            Visible = true; Width = 400; Height = 100;
        }
         private static Int32 Sum(CancellationToken ct, Int32 n) {
        Int32 sum = 0;
        for (; n > 0; n--) {
            // The following line throws OperationCanceledException when Cancel 
            // is called on the CancellationTokenSource referred to by the token
            ct.ThrowIfCancellationRequested();
            //Thread.Sleep(0);   // Simulate taking a long time
            checked { sum += n; }
        }
        return sum;
       }
        private readonly TaskScheduler m_syncContextTaskScheduler =
           TaskScheduler.FromCurrentSynchronizationContext();
        private CancellationTokenSource m_cts;
        protected override void OnMouseClick(System.Windows.Forms.MouseEventArgs e) {
            if (m_cts != null) {    // An operation is in flight, cancel it
                m_cts.Cancel();
                m_cts = null;
            } else {                // An operation is not in flight, start it
                Text = "Operation running";
                m_cts = new CancellationTokenSource();
           // This task uses the default task scheduler and executes on a thread pool thread
                var t = new Task<Int32>(() => Sum(m_cts.Token, 20000), m_cts.Token);
                t.Start();
 // These tasks use the synchronization context task scheduler and execute on the GUI thread
                t.ContinueWith(task => Text = "Result: " + task.Result,
                   CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,
                   m_syncContextTaskScheduler);
                t.ContinueWith(task => Text = "Operation canceled",
                   CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled,
                   m_syncContextTaskScheduler);
                t.ContinueWith(task => Text = "Operation faulted",
                   CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,
                   m_syncContextTaskScheduler);
            }
            base.OnMouseClick(e);
        }
}

可怜处理

在职责抛出的未处理格外都封装在System.AggregateException对象中。那一个指标会储存在格局重临的Task或Task<TResult>对象中,需求经过拜访Wait()、Result、Exception成员才能观测到那个。(所以,在造访Result之前,应先观看IsCanceled和IsFaulted属性)

假使一向不访问Task的Wait()、Result、Exception成员,那么您将永生永世注意不到那一个尤其的发出。为了扶助你检验到那一个未处理的那些,能够向TaskScheduler对象的UnobservedTaskException事件注册回调函数。每当3个Task被垃圾回收时,假设存在3个从未理会到的不行,CL索罗德的了断器线程会引发这些事件。

可在事变回调函数中调用UnobservedTaskException伊芙ntArgs对象的SetObserved()
方法来建议已经处理好了十一分,从而阻碍CL福特Explorer终止线程。然则并不推荐这么做,宁愿终止进度也毫不带着曾经毁损的意况继续运营。

   
由于委托类型定义了BeginInvoke和EndInvoke方法,所以委托项目都达成了异步编程模型。

   
由于委托类型定义了BeginInvoke和EndInvoke方法,所以委托项目都达成了异步编制程序模型。

Async /Await

在.NET Framework 4.0中添加.NET Framework
四.5中新的异步操作库(async/await),该包由多少个库组成:Microsoft.Bcl、Microsoft.Bcl.Async和Microsoft.Bcl.Build。

Install-Package Microsoft.Bcl.Async

注:asp.net
框架必须求升级.net framework框架才能运用 async/await

万一那多少个新闻是“Message : Could not load file or assembly ‘System.Core,
Version=2.0.伍.0, Culture=neutral, PublicKeyToken=7cec85d七bea77九八e,
Retargetable=Yes’ or one of its dependencies. The given assembly name or
codebase was invalid. (Exception from HRESULT: 0x8013十四七)”,

那需求您去微软官网下载.net4.0的KB2468871补丁来安装。

C# 五引进了异步函数(asynchrnous
function)的概念。平常是指用async修饰符注解的,可

饱含await表明式的格局或匿名函数;

async关键字成立了一个状态机,类似于yield
return语句;await关键字只好用来有用async修饰符表明的措施。async修饰符只可以用于再次来到Task/Task<TResult>或void的艺术。await只可以用来调用重回Task/Task<TResult>的不二等秘书籍;await会解除线程的隔断,完结调用的任务;等待职责成功后,获取结果,然后实施await关键字背后的代码;编译器会把await的表明式后的代码应用
Task.ContinueWith
装进了四起,回调时默许使用当前线程的同台上下文义务调度器;假使不利用同壹的协同上下文,必须调用Task实例的ConfigureAwait(false)方法;

await msg.Content.ReadAsStringAsync().ConfigureAwait(false);

异步方法的扬言语法与任何方式完全相同,只是要包蕴async上下文关键字。async能够出

现行反革命归来类型以前的别的职责。async修饰符在变更的代码中尚无效应,也可省略不写,它显著表明了你的预期,告诉编写翻译器能够主动搜寻await表明式,也足以查找应该转换来异步调用和await表明式的块调用。

调用者和异步方法之间是通过再次来到值来通信的。异步函数的回到类型只可以为:

Void
、Task、Task<TResult>;Task和Task<TResult>类型都意味着二个或者还未成功的操作。
Task<TResult>继承自Task。二者的差别是,Task<TResult>表示五个重返值为T类型的操作,而Task则不须要发出再次回到值。在某种意义上,你能够认为Task正是Task<void>类型;

因此将异步方法设计为能够重临void,是为着和事件处理程序兼容。

异步方法签名的自律:全数参数都不能运用out或ref修饰符。

威尼斯人线上娱乐 7

 

    贰.1 Beginxxx方法–起始实行异步操作

    贰.壹 Beginxxx方法–开头履行异步操作

         
在急需取得文件中的内容时,我们常见会选择FileStream的同台方法Read举行读取,该联合方法的概念为:

         
在须要取得文件中的内容时,大家司空眼惯会采用FileStream的一路方法Read举行读取,该联合方法的概念为:

          public override int Read(byte[] array,int offset,int count)

          public override int Read(byte[] array,int offset,int count)

         
当使用方面包车型大巴章程读取大文件的情节时,相会世堵塞UI线程,导致在文书内容并没有读取实现此前,用户不可能对窗体进行别的操作(包含关闭应用程序),那时窗体就会现出不能够响应的意况。

         
当使用方面包车型大巴主意读取大文件的故事情节时,会现出堵塞UI线程,导致在文件内容从未读取完结在此以前,用户不可能对窗体进行别的操作(包罗关闭应用程序),那时窗体就会产出不恐怕响应的景观。

          为了缓解那么些题材,微软早在.NET
壹.0的时候就建议了异步编制程序模型,并为FileStream类提供了异步方式的章程落成,即BeginRead方法。该方法会异步地执行读取操作,并再次来到达成了IAsyncResult接口的指标(该对象存款和储蓄那异步操作的音讯)。

          为了消除那个标题,微软早在.NET
一.0的时候就提议了异步编制程序模型,并为FileStream类提供了异步形式的秘籍达成,即BeginRead方法。该方法会异步地推行读取操作,并赶回达成了IAsyncResult接口的靶子(该对象存款和储蓄那异步操作的音信)。

         
下边给出了BeginRead方法的概念,大家得以从中找出它与一同方法Read的分别:

         
上边给出了BeginRead方法的概念,大家能够从中找出它与一块方法Read的界别:

          public override IAsyncResult BeginRead(byte[] array,int
offset,int numBytes,AsyncCallback userCallback,Object stateObject)

          public override IAsyncResult BeginRead(byte[] array,int
offset,int numBytes,AsyncCallback userCallback,Object stateObject)

         
从上述的异步方法的概念能够看到,该异步方法的最近2个参数与协同方法Read一致,后多少个参数userCallback和StateObject则是联合方法所不享有的。userCallback代表异步操作完毕后要求回调的不二等秘书诀,该方法必须相配AsyncCallback委托项目;stateObject则意味传递给回调方法的对象,在回调方法中,能够透过查询IAsyncResult接口的AsyncState属性来读取该指标。该异步方法之所以不会堵塞UI线程,是因为它在被调用后,会立时把控制权交还给调用线程(假如是UI线程调用了该办法,则就将控制权重返给UI线程),然后由另多少个线程去实施文书读取操作。

         
从上述的异步方法的概念能够看到,该异步方法的前方3个参数与壹块方法Read一致,后多少个参数userCallback和StateObject则是一起方法所不拥有的。userCallback表示异步操作完结后须求回调的艺术,该办法必须相配AsyncCallback委托项目;stateObject则表示传递给回调方法的目的,在回调方法中,能够因此查询IAsyncResult接口的AsyncState属性来读取该对象。该异步方法之所以不会堵塞UI线程,是因为它在被调用后,会及时把控制权交还给调用线程(要是是UI线程调用了该措施,则就将控制权重回给UI线程),然后由另3个线程去履行文书读取操作。

 

 

威尼斯人线上娱乐 ,    2.2 Endxxx方法–甘休异步操作

    2.2 Endxxx方法–甘休异步操作

         
每一遍调用Beginxxx方法后,应用程序还需调用Endxxx方法来取得操作重回的结果。Beginxxx方法所重临的,是落到实处了IAsyncResult接口的目的,该对象无须相应的联手方法重临的结果。此时还供给调用Endxxx方法来终止异步操作,并向该格局传递Beginxxx所重临的靶子。Endxxx方法重回的品类与联合方法壹致,如FileStream的EndRead方法会重返二个Int3贰品种,代表从文件流中实际读取的字节数。

         
每一遍调用Beginxxx方法后,应用程序还需调用Endxxx方法来赢得操作再次回到的结果。Beginxxx方法所再次来到的,是完成了IAsyncResult接口的对象,该目标并非相应的协同方法再次回到的结果。此时还亟需调用Endxxx方法来终结异步操作,并向该方法传递Beginxxx所再次回到的对象。Endxxx方法重回的种类与联合方法一致,如FileStream的EndRead方法会再次来到二个Int3二品种,代表从文件流中实际读取的字节数。

         
Endxxx方法有成都百货上千中方法调用,但有壹种是最常用的,即利用AsyncCallback委托来钦赐操作实现时要调用的措施,在回调方法中调用Endxxx方法来博取异步操作重临的结果。

         
Endxxx方法有很多中艺术调用,但有1种是最常用的,即利用AsyncCallback委托来钦定操作达成时要调用的主意,在回调方法中调用Endxxx方法来取得异步操作重临的结果。

 1 static void Main()
 2 {
 3     SynchronizationContext sc=SynchronizationContext.Current;
 4     AsyncMethodCaller methodCaller=new AsyncMethodCaller(DownLoadFileAsync);
 5     method.BeginInvoke(txtUrl.Text.Trim(),GetResult,null);
 6 }
 7 private static void GetRsult(IAsyncResult result)
 8 {
 9     AsyncMethodCaller caller=(AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;
10     string returnstring=call.EndInvoke(result);
11 }
 1 static void Main()
 2 {
 3     SynchronizationContext sc=SynchronizationContext.Current;
 4     AsyncMethodCaller methodCaller=new AsyncMethodCaller(DownLoadFileAsync);
 5     method.BeginInvoke(txtUrl.Text.Trim(),GetResult,null);
 6 }
 7 private static void GetRsult(IAsyncResult result)
 8 {
 9     AsyncMethodCaller caller=(AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;
10     string returnstring=call.EndInvoke(result);
11 }

 

 

三、异步编制程序模型(EAP)

3、异步编制程序模型(EAP)

   
就算眼下的异步编制程序能够化解执行耗费时间操作时界面不大概响应的难题,但APM也如出一辙存在那有个别鲜明的标题,如不援助对异步操作的打消以及不能够提供下载速度报告等。然则对于桌面应用程序而言,进程报告和收回操作的遵循是必需的,所以微软在.NET
二.0
发表时又提议了3个新的异步编制程序模型–基于事件的异步模型,即EAP(伊芙nt-based
Asynchronous Pattern)。

   
即使方今的异步编制程序能够缓解执行耗费时间操作时界面无法响应的题材,但APM也同样存在那有个别路人皆知的标题,如不协理对异步操作的吊销以及无法提供下载速度报告等。但是对于桌面应用程序而言,进程报告和撤除操作的效劳是不能缺少的,所以微软在.NET
二.0
发表时又提议了二个新的异步编制程序模型–基于事件的异步模型,即EAP(伊夫nt-based
Asynchronous 帕特tern)。

   
完结了EAP的类具有一个或多少个以Async为后缀的主意,以及对应的Completed事件,并且这个类辅助异步方法的撤消和进度报告。在.NET类库中,只有部分类达成了EAP,共一多少个。在那一八个类中,开发进度中选取最多的实在BackgroundWorker类了。

   
达成了EAP的类具有三个或三个以Async为后缀的主意,以及相应的Completed事件,并且这一个类协助异步方法的打消和速度报告。在.NET类库中,唯有部分类实现了EAP,共二十一个。在这一7个类中,开发进度中利用最多的其实BackgroundWorker类了。

    日常应用的质量为:

    平时选取的天性为:

    CancellationPending:用来提醒应用程序是还是不是已呼吁撤废后台操作;

    CancellationPending:用来提示应用程序是还是不是已呼吁裁撤后台操作;

    IsBusy:提醒异步操作是或不是正在运行;

    IsBusy:提醒异步操作是不是正在周转;

    WorkReportProgress:只是BackgrounWorker能还是不可能报告进程;

    WorkReportProgress:只是BackgrounWorker能或不能够报告进度;

   
WorkerSupportsCancellation:提示BackgroundWoker是或不是协助异步废除操作;

   
WorkerSupportsCancellation:提醒BackgroundWoker是不是扶助异步撤废操作;

    平常使用的秘籍为:

    平日利用的艺术为:

    CancelAsync:请求打消异步操作;

    CancelAsync:请求裁撤异步操作;

    ReportProgress:用于引发ProgressChanged事件;

    ReportProgress:用于引发ProgressChanged事件;

    RunWorkAsync:调用后起始施行异步操作;

    RunWorkAsync:调用后初始实践异步操作;

    常常利用到的3个事件为:

    日常应用到的三个事件为:

    DoWork:调用RunWokerAsync时接触的事件;

    DoWork:调用RunWokerAsync时接触的轩然大波;

   
ProgressChanged:调用ReportProgress时接触的事件,程序会在该事件中开始展览进程报告的换代;

   
ProgressChanged:调用ReportProgress时接触的轩然大波,程序会在该事件中展开速度报告的更新;

    RunWorkerCompleted:当异步操作已做到、被撤废或吸引这么些时被触发。

    RunWorkerCompleted:当异步操作已做到、被吊销或吸引那多少个时被触发。

    那种措施已经很少用到了,所以那边就不详细介绍了。

    那种办法已经很少用到了,所以那边就不详细介绍了。

 

 

4、TAP又是什么样?

四、TAP又是怎么?

    后边介绍了.NET提供的二种异步编程情势,分别为.NET 一.0中的APM和.NET
2.0中的EAP。即使那二种异步编制程序形式能够兑现多数景况下的异步编制程序,可是它们在MSDN文书档案上都被标明为了不推荐应用的兑现格局,因为在.NET
四.0中,微软又提供了更不难的异步编制程序达成方式–TAP,基于任务的异步方式。

    前边介绍了.NET提供的二种异步编制程序格局,分别为.NET 一.0中的APM和.NET
贰.0中的EAP。纵然那二种异步编制程序格局可以兑现多数场所下的异步编制程序,不过它们在MSDN文书档案上都被标明为了不推荐使用的落到实处情势,因为在.NET
肆.0中,微软又提供了更简短的异步编程实现格局–TAP,基于职责的异步情势。

   
该方式首要采纳System.Threading.Tasks命名空间中的Task<T>类来达成异步编制程序,所以在采用TAP此前,首先要引进System.Threading.Tasks命名空间。

   
该格局重要选择System.Threading.Tasks命名空间中的Task<T>类来实现异步编制程序,所以在行使TAP在此之前,首先要引进System.Threading.Tasks命名空间。

    基于职务的异步形式(TAP,Task-based Asynchronous
Pattern)只使用3个方法就能代表异步操作的始发和成功,而APM却必要Beginxxx和Endxxx三个章程分别代表伊始和完工,EAP则须求全部以Async为后缀的法子和三个或四个事件。在根据任务的异步格局中,只须要七个以TaskAsync为后缀的点子,通过向该办法传入CancellationToken参数,我们就足以很好地形成异步编制程序了。而且,还是能够透过IProgress<T>接口来贯彻速度报告的功效。总体来说,使用TAP会缩小大家的工作量,是代码越发从简。

    基于职务的异步格局(TAP,Task-based Asynchronous
Pattern)只使用八个艺术就能表示异步操作的起首和姣好,而APM却需求Beginxxx和Endxxx多个格局分别表示开首和甘休,EAP则供给具备以Async为后缀的方法和八个或七个事件。在根据任务的异步格局中,只须求四个以TaskAsync为后缀的办法,通过向该措施传入CancellationToken参数,我们就能够很好地做到异步编制程序了。而且,还能通过IProgress<T>接口来落实速度报告的功效。总体来说,使用TAP会减弱大家的工作量,是代码越发从简。

1 Task task=new Task(()=>{.......});
2 task.Start();
1 Task task=new Task(()=>{.......});
2 task.Start();

 

 

五、让异步编制程序So easy——C# 5.0中的async和await

五、让异步编制程序So easy——C# 5.0中的async和await

    纵然.NET 一.0和.NET 二.0和.NET
4.0都对异步编制程序做了很好的扶助,微软也慢慢地接纳异步编制程序变得不难,但微软认为现有的劳作还不够,它愿意使异步编制程序的费用进度更是简化,所以在.NET
四.5中,微软又提议了async和await八个相当重要字来支撑异步编程。

    固然.NET 一.0和.NET 2.0和.NET
四.0都对异步编制程序做了很好的支撑,微软也日益地接纳异步编制程序变得简单,但微软认为现有的干活还不够,它愿意使异步编制程序的开支进度更为简化,所以在.NET
4.5中,微软又建议了async和await三个基本点字来扶助异步编制程序。

    那也是如今.NET
Framework中最简易的异步编制程序完成方式,因为运用那个四个关键字展开异步编制程序,考虑方式和贯彻同步编制程序时的一心等同。

    那也是如今.NET
Framework中最简易的异步编制程序完结格局,因为运用这么些五个重大字展开异步编程,思虑格局和促成联机编制程序时的一心相同。

   
async和await关键字不会让调用方法运行在新线程中,而是将艺术分割成多个部分(片段的无尽出今后艺术内部接纳await关键字的地点处),并使当中一些片段能够异步运维。await关键字处的代码片段是在线程池线程上运营的,而全方位艺术的调用确实联合的。所以,使用此方法编制程序不用怀恋跨线程访问UI控件的题材,从而大大下降了异步编程的出错率。

   
async和await关键字不会让调用方法运维在新线程中,而是将艺术分割成几个部分(片段的无尽出现在艺术内部采纳await关键字的地方处),并使内部部分部分能够异步运转。await关键字处的代码片段是在线程池线程上运维的,而整整艺术的调用确实联合的。所以,使用此格局编制程序不用思考跨线程访问UI控件的标题,从而大大下降了异步编制程序的出错率。


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图