锄奸队成员 發表於 2019-6-12 18:45:00

C#当中的BeginInvoke和EndInvoke

<p>我们已经知道 C#当中 存在async/await 、BackGroudWorker类以及TPL(任务并行库)。当然C#还有一些旧的模式来支持异步编程。参考《C#图解教程》</p>
<p><strong>&nbsp;1. BeginInovke和EndInvoke简单介绍</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">delegate</span> <span style="color: rgba(0, 0, 255, 1)">long</span> MyDel(<span style="color: rgba(0, 0, 255, 1)">int</span> first, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> second);
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> Sum(<span style="color: rgba(0, 0, 255, 1)">int</span> x, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> y)
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">    {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>         Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">------Inside Sum@{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, DateTime.Now.ToString());
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>         Thread.Sleep(<span style="color: rgba(128, 0, 128, 1)">2000</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>         <span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">11</span>
<span style="color: rgba(0, 128, 128, 1)">12</span>   <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)">    {
</span><span style="color: rgba(0, 128, 128, 1)">14</span>         MyDel del = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyDel(Sum);
</span><span style="color: rgba(0, 128, 128, 1)">15</span>
<span style="color: rgba(0, 128, 128, 1)">16</span>         Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Before BeginInvoke---@{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, DateTime.Now.ToString());
</span><span style="color: rgba(0, 128, 128, 1)">17</span>         IAsyncResult iar = del.BeginInvoke(<span style="color: rgba(128, 0, 128, 1)">3</span>, <span style="color: rgba(128, 0, 128, 1)">5</span>, <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">18</span>         Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">After BeginInvoke@{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, DateTime.Now.ToString());
</span><span style="color: rgba(0, 128, 128, 1)">19</span>
<span style="color: rgba(0, 128, 128, 1)">20</span>         Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Doing stuff@{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, DateTime.Now.ToString());
</span><span style="color: rgba(0, 128, 128, 1)">21</span>
<span style="color: rgba(0, 128, 128, 1)">22</span>         <span style="color: rgba(0, 0, 255, 1)">long</span> result =<span style="color: rgba(0, 0, 0, 1)"> del.EndInvoke(iar);
</span><span style="color: rgba(0, 128, 128, 1)">23</span>         Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">End Invoke@{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, DateTime.Now.ToString());
</span><span style="color: rgba(0, 128, 128, 1)">24</span>
<span style="color: rgba(0, 128, 128, 1)">25</span>         Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">After EndInvoke:{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, result);
</span><span style="color: rgba(0, 128, 128, 1)">26</span>
<span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)">      Console.ReadKey();
</span><span style="color: rgba(0, 128, 128, 1)">28</span>
<span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">30</span> }</pre>
</div>
<p>如上代码,定义了一个委托 MyDel ,并且在调用的时候把Sum方法传给了它的对象。一般情况下我们调用这个委托对象,它就会调用他调用列表中包含的方法。就想调用方法一样,这是同步完成的。</p>
<p>&nbsp;</p>
<p>但是如果委托对象在调用列表中只有一个方法(引用方法),它就可以异步的去执行这个方法。BeginInovke和EndInvoke就是用来做这个事的。我们可以用如下的方式使用:</p>
<p>①当我们调用BeginInvoke方法的时候,他开始在一个独立的线程上执行引用方法,并且立即返回到原始线程。原始线程可以继续,而引用方法会在想吃的线程中并行执行。</p>
<p>②当程序希望获取已完成的异步方法的结果时,可以检查BeginInvoke返回的IAsyncResult的IsCompleted属性,或者调用委托的EndInvoke方法来等待委托执行完成。</p>
<p>&nbsp;</p>
<p>上面的使用过程就引出的三种模式:</p>
<p>①等待-直到完成 原始线程在发起了异步方法以及做了一些其他处理之后,原始线程就中断并且等待异步方法执行完成之后再继续。</p>
<p><img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612183751406-1141176348.png"></p>
<p>②轮询 ,原始线程定期检查发起的线程是否完成,如果没有则可以继续做其他的事情,</p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612183801459-1891051798.png"></p>
<p>③回调 原始线程一直执行,无需等待或者检查发起的线程是否完成,发起的线程中的引用发放完成之后,发起的线程会调用回调方法,由回调方法在调用的EndInvoke之前处理异步方法的结果。</p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612183811696-1217635410.png"></p>
<p><strong>2.BeginInovke和EndInvoke详细</strong></p>
<p>&nbsp;</p>
<p><strong>对于 BeginInvoke 方法,有几个注意的地方</strong></p>
<p>① 我们可以根据上面的代码知道,BeginInvoke的参数包含如下两个部分</p>
<p>l&nbsp; 引用方法的参数</p>
<p>l&nbsp; CallBack参数和State参数</p>
<p>②BeginInvoke 会在线程池中找到一个线程,让引用方法运行在该线程上</p>
<p>③BeginInvoke 返回给调用线程一个实现IAsyncResult接口的对象的引用。这个接口引用包含了在线程池线程中运行的异步方法的状态。可以判断这个状态来确定异步方法是否结束。&nbsp;</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"> // 3和5是引用方法参数,两个null分别是Callback参数和State参数
// iar是新线程的信息
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
</pre>
</div>
<p><strong>对于 EndInvoke 方法,有几个注意的地方</strong></p>
<p>①他的参数是上面BegionInvoke返回的IAsyncResult接口的引用对象,传入这个对象是便于EndInvoke去找到引用方法运行的线程。并且这个参数置于参数列表最后一个。EndInvoke提供了从异步方法调用的所有输出,包括ref和out参数。如果委托的引用方法有ref和out参数,他们必须包含在EndInvoke的参数列表当中</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">IAsyncResult iar2 = del2.BeginInvoke(3, 5, out res, null, null);
del2.EndInvoke(out res, iar2); </pre>
</div>
<p>②如果线程已经退出了,EndInvoke会做如下事情:</p>
<p>  清理退出的线程的状态并且释放资源</p>
<p>  找到引用方法的返回值,并作为自己的返回值</p>
<p>③如果EndInovke发现线程还在运行中,那么调用线程就会停止并等待,直到清理完毕并返回值。</p>
<p>④因为EndInvoke会去清理线程信息,所以BeginInvoke和EndInvoke必须成对使用。</p>
<p>⑤如果异步方法出现异常,那么在调用EndInvoke的时候会抛出异常。</p>
<p>&nbsp;</p>
<p><strong>3.AsyncResult类</strong></p>
<p>上面说BeginInvoke方法返回了一个IAsyncResult接口的引用对象(内部是AsyncResult类的对象),AsyncResult类型表现了异步方法的状态。下面是这类的主要组成部分:</p>
<p><img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612183828661-890299304.png"></p>
<p><img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612183833707-511037248.png"></p>
<p><strong>4.三种模式</strong></p>
<p><strong>① 等待-直到完成 (比较简单的模式)</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">//开始执行异步方法
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
//Do Something 耗时
del2.EndInvoke(iar);
</pre>
</div>
<p>像上面的代码,BeginInvoke之后,做了一些事情,然后调用EndInvoke来处理结果,这种方式就是等待-直到完成的模式。</p>
<p>&nbsp;<strong>②轮询模式</strong></p>
<p>轮询模式中,原始的线程发起了异步的方法调用,做一些事情,然后使用IAsyncResult中的IsComplete熟悉来定期检查开启的线程是否完成。如果未完成就在去做一些其他事情。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">delegate</span> <span style="color: rgba(0, 0, 255, 1)">long</span> MyDel(<span style="color: rgba(0, 0, 255, 1)">int</span> first, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> second);

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
{
    </span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">long</span> Sum(<span style="color: rgba(0, 0, 255, 1)">int</span> x, <span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> y)
    {
      Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">--Inside Sum@{0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, DateTime.Now.ToString());
      Thread.Sleep(</span><span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> x +<span style="color: rgba(0, 0, 0, 1)"> y;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
    {
      MyDel del </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyDel(Sum);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开始执行异步方法</span>
      IAsyncResult iar = del.BeginInvoke(<span style="color: rgba(128, 0, 128, 1)">3</span>, <span style="color: rgba(128, 0, 128, 1)">5</span>, <span style="color: rgba(0, 0, 255, 1)">null</span>, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">轮询开始</span>
      <span style="color: rgba(0, 0, 255, 1)">while</span> (!<span style="color: rgba(0, 0, 0, 1)">iar.IsCompleted)
      {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">未完成,执行下面的语句</span>
            <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">long</span> i = <span style="color: rgba(128, 0, 128, 1)">0</span>; i &lt; <span style="color: rgba(128, 0, 128, 1)">20000000</span>; i++<span style="color: rgba(0, 0, 0, 1)">)
                ;
      }
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">执行完成调用EndInvoke获取结果</span>
      <span style="color: rgba(0, 0, 255, 1)">long</span> result =<span style="color: rgba(0, 0, 0, 1)"> del.EndInvoke(iar);
      Console.ReadKey();
    }</span></pre>
</div>
<p><strong>③回调模式</strong></p>
<p>前两种都是主动方式的,原始线程一直在监控这新开启的线程。但是回调是被动的,一旦原始线程发起了异步方法,它就自己管自己了,不在考虑同步。</p>
<p>当异步方法调用结束之后,系统调用一个用户自定义的方法来处理结果,并且调用委托的EndInvoke方法。这个用户自定义的方法就是回调方法。</p>
<p>&nbsp;</p>
<p>上面的BegionInvoke中写过,他会有两个参数一个Callback参数和一个State参数.</p>
<p>CallBack参数:是回调方法的名称。</p>
<p>State参数:可以是null,或者传入回调方法的一个对象的引用。我们可以用IAsyncResult参数的AsyncState属性来获取这个对象,参数的类型是object。</p>
<p><img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612184116216-111931530.png"></p>
<p><strong>a.回调方法:</strong></p>
<p><strong>回调方法的签名和返回类型必须和 AsyncCallback委托类型所描述的形式一致。</strong></p>
<p>&nbsp;<img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612184132235-645702555.png"></p>
<p>两种方式,构建这个AsyncCallback参数</p>
<p>new AsyncCallback 对象  </p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone),null);
</pre>
</div>
<p>直接传回调方法的名称</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">IAsyncResult iar = del.BeginInvoke(3, 5, CallWhenDone, null);
</pre>
</div>
<p>其中 CallWhenDone 如下:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">static void CallWhenDone(IAsyncResult iar)
{
    AsyncResult ar = (AsyncResult)iar;
    MyDel del = (MyDel)ar.AsyncDelegate;
    //回调方法中调用了EndInvoke
    long result = del.EndInvoke(iar);
}
</pre>
</div>
<p><strong>b.在回调方法中调用EndInvoke</strong></p>
<p>上面代码中,在回调中使用了EndInvoke,上文中说到 EndInvoke的调用,是委托的调用,并且需要传入一个IAsyncResult的接口对象的引用。</p>
<p>&nbsp;所以想要在回调方法里面,调用这个EndInvoke,就得拿到两个东西一个是委托对象、一个是IAsyncResult,由于我们AsyncCallback委托本身就是必须要传入IAsyncResult 的,所以这个比较容易,剩下的就是委托对象本身了。在AsyncResult类小节里面我看到,它里面存着一个 AsyncDelegate(它就是委托对象的引用),还有就是 IAsyncResult接口对象在内部就是AsyncResult类对象。所以才可以像上main的代码,通过强制类型转换得到MyDel的对象。</p>
<p><img src="https://img2018.cnblogs.com/blog/785993/201906/785993-20190612184320810-871234492.png"></p>
<p>第二种方法就是如果State参数没有用处,可以通过State参数,把委托的对象传过去。</p>
<p>调用的地方,最后一个参数传入del</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone),del);
</pre>
</div>
<p>回调方法:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">static void CallWhenDone(IAsyncResult iar)
{
    MyDel del = (MyDel)iar.AsyncState;
    long result = del.EndInvoke(iar);
}</pre>
</div>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/dcz2015/p/11011507.html
頁: [1]
查看完整版本: C#当中的BeginInvoke和EndInvoke