<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>并行处理 - 老赵点滴 - 追求编程之美</title>
    <link>http://blog.zhaojie.me/parallel/</link>
    <description>先做人，再做技术人员，最后做程序员。打造国内最好的.NET技术博客。</description>
    <language>zh-cn</language>
    <managingEditor>jeffz@live.com (老赵)</managingEditor>
    <webMaster>jeffz@live.com (老赵)</webMaster>
    <pubDate>Wed, 24 Jun 2009 04:46:00 GMT</pubDate>
    <lastBuildDate>Wed, 24 Jun 2009 04:46:00 GMT</lastBuildDate>
    <ttl>60</ttl>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/extension/">项目扩展</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>基于Jscex.Async的JavaScript动画/游戏</title>
      <link>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html</link>
      <guid>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html</guid>
      <description>&lt;p&gt;首先和大家宣布一个消息，Jscex的代码已经&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;提交至Github上&lt;/a&gt;了，感兴趣的朋友下载来Dog Fooding一把，并欢迎提出反馈意见。Jscex受到F#计算表达式的启发，是一个面向JavaScript语言的monadic扩展，最常见的用途便是编写异步程序，尤其是逻辑复杂的异步程序。不过除此之外，使用这套异步库来编写动画或是游戏也是十分容易的事情。例如，一个人物的走动或是爆炸效果，其实可以视为一个贴图随时间不断变化的过程。这个变化的过程是异步的，但是有了Jscex.Async，我们只需使用最直接的同步形式编写代码就行了。&lt;/p&gt;

&lt;p&gt;例如这里有个&lt;a href="http://files.zhaojie.me/demos/jscex/samples/async/bullet.html"&gt;我用一个多小时编写的示例&lt;/a&gt;（需要支持canvas的浏览器，例如IE 9，FireFox，Chrome，Safari等等），类似于“是男人就撑过20秒”，其中有这么一段代码：&lt;/p&gt;

&lt;pre class="code"&gt;BulletGame.prototype._bulletFlyAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(f) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;options = &lt;span style="color: blue"&gt;this&lt;/span&gt;._options;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;bullet = {pos: {x: 0, y: 0}};

    &lt;span style="color: blue"&gt;this&lt;/span&gt;._addBullet(bullet);
    
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; &lt;span style="color: blue"&gt;this&lt;/span&gt;._playing &amp;amp;&amp;amp; &lt;span style="color: blue"&gt;this&lt;/span&gt;._inArea(bullet.pos); t += 20) {
        $await(&lt;span style="color: blue"&gt;this&lt;/span&gt;._timer.setTimeoutAsync(20));
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._tryHit(bullet.pos); &lt;span style="color: green"&gt;// 判断是否击中&lt;/span&gt;
        bullet.pos = f(t); &lt;span style="color: green"&gt;// 改变子弹位置&lt;/span&gt;
    }

    &lt;span style="color: blue"&gt;this&lt;/span&gt;._removeBullet(bullet);
}));&lt;/pre&gt;

&lt;p&gt;这段代码是一个异步程序，表示一颗子弹的飞行过程。子弹的飞行轨迹（某一时刻的位置）由函数f来决定，因此我们只需要写一个for循环，并且在循环内部“暂停”一段时间就行了，就这么简单。在循环之前，我们将子弹添加到一个容器里，这样在一个不断循环的paint方法里就会把这颗子弹绘制在canvas上面。循环结束后，我们将子弹从容器中移出。这一切都十分顺其自然，虽然这段代码的执行过程完全是异步的。&lt;/p&gt;

&lt;p&gt;Jscex可以使用各种方式与现有代码组合至一块儿，例如这里我为BulletGame的prototype对象扩展了一个方法，为此我晚上还改进了一下Jscex的编译逻辑，以及Jscex.Async的实现，其目的就是在_bulletFlyAsync这样的函数内部保留this对象在JavaScript语义上的正确性。JavaScript语言中this引用的动态特征是种很强大的特性，但是对于“语言改造者”来说，在一个充满回调的过程中保持语义正确并不是件十分容易的事情（当然，也不算十分困难，只要“想明白”即可）。上面的代码经过编译之后，就类似于我们直接编写了这样的代码（暂时如此，未来会有修改和优化）：&lt;/p&gt;

&lt;pre class="code"&gt;BulletGame.prototype._bulletFlyAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;(f) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Start(&lt;span style="color: blue"&gt;this&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;options = &lt;span style="color: blue"&gt;this&lt;/span&gt;._options;
        &lt;span style="color: blue"&gt;var &lt;/span&gt;bullet = { pos: { x: 0, y: 0} };
        &lt;span style="color: blue"&gt;this&lt;/span&gt;._addBullet(bullet);
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Combine(
            $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Loop(
                    &lt;span style="color: blue"&gt;function &lt;/span&gt;() { &lt;span style="color: blue"&gt;return this&lt;/span&gt;._playing &amp;amp;&amp;amp; &lt;span style="color: blue"&gt;this&lt;/span&gt;._inArea(bullet.pos); },
                    &lt;span style="color: blue"&gt;function &lt;/span&gt;() { t += 20; },
                    $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(&lt;span style="color: blue"&gt;this&lt;/span&gt;._timer.setTimeoutAsync(20), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                            &lt;span style="color: blue"&gt;this&lt;/span&gt;._tryHit(bullet.pos);
                            bullet.pos = f(t);
                            &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
                        });
                    })
                );
            }),
            $async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;._removeBullet(bullet);
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
            })
        );
    });
};&lt;/pre&gt;

&lt;p&gt;此外，JavaScript是一个很容易编写此类动画或是游戏的平台，因为它所有的代码都在UI线程上执行，因此不会有任何多线程方面的问题。例如上面的代码，我们可以放心地向容器里添加或删除“子弹”对象，试想在一个并发环境里构造一个线程安全的双向链表是件多么麻烦的事情。&lt;/p&gt;

&lt;p&gt;最大的问题其实是浏览器环境里的性能问题，不过我对此并不怎么担心，因为V8已经给我们开了个好头，并且还在&lt;a href="http://blog.chromium.org/2010/12/new-crankshaft-for-v8.html"&gt;不断前进&lt;/a&gt;。此外对于动画和游戏来说，最大的性能问题更可能是canvas的绘图性能。现在这个简单的例子性能自然不会有太大问题，不过一旦加上贴图和素材，性能问题就会凸现出来了。对于目前的示例，我在安装Windows 7的台式机里试验了多种浏览器，帧数（fps）各有高低：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox：110左右 &lt;/li&gt;

  &lt;li&gt;Chrome：210左右 &lt;/li&gt;

  &lt;li&gt;IE 9：300至800不等，比较常见的是500左右 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而在我的MBP上：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Firefox：70左右 &lt;/li&gt;

  &lt;li&gt;Safari：60左右 &lt;/li&gt;

  &lt;li&gt;Chrome：190左右 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;此外还有同事测试了Safari最新的Nightly Build，帧数与Chrome不相上下。从中可以看出，IE 9的表现最为出色，可见它的GPU加速的确不是在吹牛的。值得一提的是，如果图像上没有子弹，那么帧数大约只有60出头，一旦同时出现了几十颗子弹，帧数变会飙升至500以上，峰值甚至可以到800。由此推测，IE 9也是在“按需”使用GPU，十分周全。&lt;/p&gt;

&lt;p&gt;Jscex的源码及示例都在Github上，欢迎尝试。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/animations-and-games-based-on-jscex-async.html#comments</comments>
      <pubDate>Wed, 15 Dec 2010 17:03:28 GMT</pubDate>
      <lastBuildDate>Thu, 16 Dec 2010 05:14:09 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>演出季上“异步编程模型的演变”幻灯片</title>
      <link>http://blog.zhaojie.me/2010/12/event-season-async-evolution-slides.html</link>
      <guid>http://blog.zhaojie.me/2010/12/event-season-async-evolution-slides.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2010/11/the-coming-talks-and-jscex.html"&gt;演出季&lt;/a&gt;终于过去了，现在就来做一个收尾吧。这次的主题是“异步编程模型的演变”，主要回顾了微软在.NET平台上异步编程上的进化：基于回调，基于迭代生成器，基于类库，基于语言。不过这样的编程模型其实并非微软独有，而是一些运用比较广泛的异步编程方式，因此在SD 2.0大会上我其实完全用JavaScript进行演示。从结果上来看，除了最早的TUP，其他两场演讲（&lt;a href="http://www.net-china.org/"&gt;.NET技术大会&lt;/a&gt;和&lt;a href="http://sd2china.csdn.net/"&gt;SD 2.0&lt;/a&gt;）的反响都不错。&lt;/p&gt;

&lt;p&gt;严格说来，这场演讲在过去一个月中讲了四次，其中第一次是在CSDN举办的&lt;a href="http://tup.csdn.net/"&gt;TUP&lt;/a&gt;活动上。您可能注意到“异步编程”是我后半年关注的重点，例如在&lt;a href="http://nbazaar.org"&gt;创新院赞助的nBazaar交流会&lt;/a&gt;上我分享的话题都是关于异步编程的，这次要在1小时内覆盖之前两场演讲，自然信息量很大。于是在TUP活动上的演讲便不太理想，因为内容实在太多，结果绝大部分内容几乎都是匆匆扫过。不幸中的万幸，这次演讲的幻灯片倒成了一个“阅读材料”（&lt;a href="http://files.zhaojie.me/slides/async-evolution/async-evolution-full.pdf"&gt;下载&lt;/a&gt;）。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_6103768"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="The Evolution of Async-Programming on .NET Platform (TUP, Full)" href="http://www.slideshare.net/jeffz/async-evolutionfull"&gt;The Evolution of Async-Programming on .NET Platform (TUP, Full)&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse6103768" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=async-evolution-full-101210113950-phpapp02&amp;stripped_title=async-evolutionfull&amp;userName=jeffz" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse6103768" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=async-evolution-full-101210113950-phpapp02&amp;stripped_title=async-evolutionfull&amp;userName=jeffz" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;我将异步编程模型的演变过程分为四个部分：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;基于回调：&lt;/strong&gt;这是最传统的异步编程模型，“回调”是“异步”的天然属性，例如.NET里的Begin/End异步模型或是基于事件的异步模型。在前端开发中，XMLHttpRequest或是DOM事件，事实上都是基于事件的异步编程模型。这种编程模型破坏了代码的局部性，写起来非常麻烦，更别说是异步操作的组合、异常处理、取消等操作了。 &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;基于迭代生成器：&lt;/strong&gt;我把这个单独提出来说，是因为迭代生成器几乎已经成为现代语言的标配了，&lt;a href="http://blog.zhaojie.me/2010/11/asynciterator-the-asyncenumerator-in-javascript.html"&gt;在JavaScript 1.7里也有相应的特性&lt;/a&gt;——自然，可怜的Java语言是不曾提供支持的。有了迭代生成器，我们就可以在发起异步操作的时候，将控制权交给外部，由外部来决定代码什么时候继续执行，这就在一定程度上保持了代码的局部性。 &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;基于类库：&lt;/strong&gt;由于异步编程十分困难，有时候语言层面的支持有限，聪明的程序员们想尽了各种办法来简化异步编程。其中主要的办法就是总结出一种异步模型，并围绕这种模型提供一种异步类库。其中的典型便是&lt;a href="http://blog.zhaojie.me/2010/09/async-programming-and-reactive-framework.html"&gt;“推集合模型”以及“响应式编程”&lt;/a&gt;。只可惜想要基于死板的Java语言开发出好用的异步类库也总是使不出劲，与它形成鲜明对比的便是Scala语言，基于同样的平台，生产力却天下地下。 &lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;基于语言：&lt;/strong&gt;有时候，语言设计者会直接在语言层面上简化异步编程的问题，其中的典型便是Erlang语言以及&lt;a href="http://blog.zhaojie.me/2010/10/pdc2010-the-future-of-csharp-and-vb-by-anders-hejlsberg-1.html"&gt;下个版本的C#&lt;/a&gt;。不过这里我主要讨论的是&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;F#语言里的异步工作流&lt;/a&gt;。不过严格来说，F#的异步工作流是个类库，它使用了F#的“计算表达式”特性，这才是个语言特性。我参考了F#的异步工作流，开发Jscex（一个JavaScript至JavaScript编译器）以及Jscex.Async组件（基于Jscex的异步类库）。 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由于TUP上的失策，我为一个星期后的.NET技术大会上重新组织了内容，减少了“代码赏析”的数量，增加了演示用的示例。我的演示是在Mac OS上基于Mono 2.8和MonoMac编写的带有Mac OS原生界面的应用程序，以此体现异步编程对于各平台上UI编程的重要性。有了TUP的经验，这次的演讲反响不错，我也被评为这次会议Top 5讲师。幻灯片如下（&lt;a href="http://files.zhaojie.me/slides/async-evolution/async-evolution-netchina.pdf"&gt;下载&lt;/a&gt;）：&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_6103757"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="The Evolution of Async-Programming on .NET Platform (.Net China, C#)" href="http://www.slideshare.net/jeffz/async-evolutiondotnetconf"&gt;The Evolution of Async-Programming on .NET Platform (.Net China, C#)&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse6103757" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=async-evolution-dotnetconf-101210113849-phpapp02&amp;stripped_title=async-evolutiondotnetconf&amp;userName=jeffz" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse6103757" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=async-evolution-dotnetconf-101210113849-phpapp02&amp;stripped_title=async-evolutiondotnetconf&amp;userName=jeffz" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;我为SD 2.0大会使用JavaScript重新编写了所有的示例，并在创新院内部的分享交流会上进行了试讲。试讲的反馈是“干货太多”，这使得我重新提炼了一下示例。不过在大会的前一天，我还是补充了一个基于node.js开发的最简单的静态文件服务器，以此表示Jscex并非只能在浏览器里使用。幻灯片如下（&lt;a href="http://files.zhaojie.me/slides/async-evolution/async-evolution-sd2.pdf"&gt;下载&lt;/a&gt;）：&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_6103746"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="The Evolution of Async-Programming (SD 2.0, JavaScript)" href="http://www.slideshare.net/jeffz/async-evolutionsd2"&gt;The Evolution of Async-Programming (SD 2.0, JavaScript)&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse6103746" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=async-evolution-sd2-101210113722-phpapp01&amp;stripped_title=async-evolutionsd2&amp;userName=jeffz" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse6103746" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=async-evolution-sd2-101210113722-phpapp01&amp;stripped_title=async-evolutionsd2&amp;userName=jeffz" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;有趣的是，在我之前的一场演讲接近尾声的时候，会场里大约只坐了一半人，因此我一开始还担心没人来听。幸运的是在演讲开始时位子已经基本坐满了，后来还有一些人站在一旁。演讲之后的反馈很少，不过都是正面的。会后我还和业界的一些JavaScript大牛交流了一下Jscex，这也是我接下来一段时间的一个工作重点，希望能够做大做好。&lt;/p&gt;

&lt;p&gt;这个演出季已经过去了，接下来又到了nBazaar交流会的时间了。第三届交流会将在1月15日举行，具体消息将在不久之后公开，敬请关注。我们又准备了四场有价值的演讲，一定能让您满意。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/event-season-async-evolution-slides.html#comments</comments>
      <pubDate>Sat, 11 Dec 2010 14:01:05 GMT</pubDate>
      <lastBuildDate>Mon, 13 Dec 2010 07:16:55 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <title>适合JavaScript 1.7中迭代生成器的异步编程机制</title>
      <link>http://blog.zhaojie.me/2010/12/javascript-17-yield-async-programming.html</link>
      <guid>http://blog.zhaojie.me/2010/12/javascript-17-yield-async-programming.html</guid>
      <description>&lt;p&gt;&lt;a href="http://blog.zhaojie.me/2010/11/asynciterator-the-asyncenumerator-in-javascript.html"&gt;上篇文章&lt;/a&gt;我提出了一种基于JavaScript 1.7中迭代生成器（yield）的异步编程方式，它可以让混乱的异步代码逻辑变得清晰一些。不过之前的AsyncIterator其实是对&lt;a href="http://blog.zhaojie.me/2010/07/why-java-sucks-and-csharp-rocks-6-yield.html"&gt;基于C# 2.0的AsyncEnumerator&lt;/a&gt;的仿制品，在公司的分享会上进行交流以后，同事&lt;a href="http://hax.javaeye.com/"&gt;hax&lt;/a&gt;提出其实可以实现地更漂亮一些。在他的提示下，我了解到JavaScript 1.7中不同于C# 2.0里的特性，因而对这种异步编程机制提出了改进。只可惜yield特性被ECMAScript 5排除了，这实在可以说是&lt;a href="http://en.wikipedia.org/wiki/Design_by_committee"&gt;委员会设计模式&lt;/a&gt;的又一次伟大胜利。&lt;/p&gt;

&lt;h1&gt;JavaScript 1.7的yield&lt;/h1&gt;

&lt;p&gt;与C#中yield功能不同的是，JavaScript 1.7的yield语句可以让生成器内部获得一个值。例如这样的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;numbers = &lt;span style="color: blue"&gt;function &lt;/span&gt;(min, max) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = min; i &amp;lt;= max; i++) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;sum = &lt;span style="background-color: yellow; color: blue"&gt;yield&lt;/span&gt; i;
        document.write(sum + &lt;span style="color: maroon"&gt;&amp;quot;&amp;lt;br /&amp;gt;&amp;quot;&lt;/span&gt;);
    }
}

&lt;span style="color: blue"&gt;var &lt;/span&gt;iterator = numbers(1, 10);
&lt;span style="color: blue"&gt;var &lt;/span&gt;sum = iterator.next();
&lt;span style="color: blue"&gt;try &lt;/span&gt;{
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;i = iterator.&lt;font color="#ff0000"&gt;send&lt;/font&gt;(sum);
        document.write(i + &lt;span style="color: maroon"&gt;&amp;quot; - &amp;quot;&lt;/span&gt;);
        sum += i;
    }
} &lt;span style="color: blue"&gt;catch &lt;/span&gt;(err &lt;span style="color: blue"&gt;if &lt;/span&gt;err &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;StopIteration) { }&lt;/pre&gt;

&lt;p&gt;执行后的结果是：&lt;/p&gt;

&lt;pre class="code"&gt;1
2 - 3
3 - 6
4 - 10
5 - 15
6 - 21
7 - 28
8 - 36
9 - 45
10 - 55&lt;/pre&gt;

&lt;p&gt;在JavaScript 1.7中，yield除了停止流程，将控制权交给外部之外，还能够返回一个值。这个值由外部控制的代码send至迭代器内部——不过，“启动”一个迭代器则不能使用带参数的send。如上面的代码，我们在while循环外使用next启动一个迭代器。&lt;/p&gt;

&lt;h1&gt;异步编程模型&lt;/h1&gt;

&lt;p&gt;在上一篇文章中，我们把异步编程模型统一为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var&lt;/span&gt; beginXxx = &lt;span style="color: blue"&gt;function&lt;/span&gt; (arg0, arg1, ..., callback) { ... }&lt;/pre&gt;

&lt;p&gt;调用这个异步方法时，我们需要显示地提供一个callback回调函数。而如今我们已经有了进一步的打算，则需要对其进行修改，如下所示：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;xxxAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;(arg1, arg2, ...) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{
        start: &lt;span style="color: blue"&gt;function &lt;/span&gt;(callback) { ... }
    }
}&lt;/pre&gt;

&lt;p&gt;简单地说，以前的beginXxx方法会直接发起一个异步请求，而如今的xxxAsync方法，则是返回一个表示“异步任务”的对象，这个对象上有一个start方法，接受一个callback函数作为参数，并启动这个异步任务。待异步任务完成时，使用callback函数发起通知并回传结果。基于这个异步模型，我们可以封装一些常用的“异步任务”，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;sleepAsync = &lt;span style="color: blue"&gt;function &lt;/span&gt;(ms) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{
        start : &lt;span style="color: blue"&gt;function &lt;/span&gt;(callback) {
            window.setTimeout(callback, ms);
        }
    };
}&lt;/pre&gt;

&lt;p&gt;sleepAsync获得一个“休眠”的异步任务，执行时便会使用setTimeout计时回调。另一个常见的异步任务则是AJAX请求：&lt;/p&gt;

&lt;pre class="code"&gt;XMLHttpRequest.prototype.receiveAsync = &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;_this = &lt;span style="color: blue"&gt;this&lt;/span&gt;;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{
        start : &lt;span style="color: blue"&gt;function&lt;/span&gt;(callback) {
            _this.onreadystatechange = &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.readyState == 4) {
                    callback(_this.responseText);
                }
            }

            _this.send();
        }
    };
}&lt;/pre&gt;

&lt;p&gt;我为XMLHttpRequest做了扩展，它的receiveAsync方法将获得一个异步任务，这个异步任务的执行结果便是这个请求的responseText值。&lt;/p&gt;

&lt;h1&gt;辅助类：AsyncTask&lt;/h1&gt;

&lt;p&gt;之前的辅助对象是AsyncIterator，而现在我们只需根据一个迭代器新建一个异步任务即可：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;AsyncTask = &lt;span style="color: blue"&gt;function&lt;/span&gt;(iterator) {
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._iterator = iterator;
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._callback = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
}
AsyncTask.prototype._loop = &lt;span style="color: blue"&gt;function&lt;/span&gt;(result) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;_this = &lt;span style="color: blue"&gt;this&lt;/span&gt;;
    &lt;span style="color: blue"&gt;try &lt;/span&gt;{
        &lt;span style="color: blue"&gt;var &lt;/span&gt;task = &lt;span style="color: blue"&gt;this&lt;/span&gt;._iterator.send(result);
        task.start(&lt;span style="color: blue"&gt;function&lt;/span&gt;(r) { _this._loop(r); });
    } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(err &lt;span style="color: blue"&gt;if &lt;/span&gt;err &lt;span style="color: blue"&gt;instanceof &lt;/span&gt;StopIteration) {  
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;._callback) &lt;span style="color: blue"&gt;this&lt;/span&gt;._callback();
    }
}
AsyncTask.prototype.start = &lt;span style="color: blue"&gt;function&lt;/span&gt;(callback) {
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._callback = callback;
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._loop();
}&lt;/pre&gt;

&lt;p&gt;作为符合我们异步编程模型的对象，AsyncTask也包含一个start方法，它会调用_loop，在_loop中则从迭代器里获得下一个异步任务，启动，并在它执行结束时继续调用_loop自身。在新的异步模型中，构建一个辅助类库也变得非常容易。&lt;/p&gt;

&lt;h1&gt;示例1：移动HTML元素&lt;/h1&gt;

&lt;p&gt;还是使用上次的例子。实现一个移动HTML元素的逻辑其实很简单，只要根据一个时间间隔不断地改变其top和left即可。例如这样：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// Pseudocode, cannot work
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;move = &lt;span style="color: blue"&gt;function &lt;/span&gt;(e, startPos, endPos, duration) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
        e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
        e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
        sleep(50); &lt;span style="color: #006400"&gt;// cannot sleep
    &lt;/span&gt;}

    e.style.left = endPos.x;
    e.style.top = endPos.y;
}&lt;/pre&gt;

&lt;p&gt;只可惜上面这段代码是无法运行的，因为在浏览器里我们没有任何手段让当前的工作线程暂停，我们没有一个阻塞的同步的sleep方法。不过我们现在有了sleepAsync方法可以提供一个异步任务。因此，moveAsync方法只能这样编写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveAsync = &lt;span style="color: blue"&gt;function&lt;/span&gt;(e, startPos, endPos, duration) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;{
        start: &lt;span style="color: blue"&gt;function&lt;/span&gt;(callback) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0;
            &lt;span style="color: blue"&gt;var &lt;/span&gt;loop = &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(t &amp;lt; duration) {
                    t += 50;
                    e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
                    e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
                    sleepAsync(50).start(loop);
                } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
                    &lt;span style="color: blue"&gt;if &lt;/span&gt;(callback) callback();
                }
            }
        
            loop();
        }
    };
}&lt;/pre&gt;

&lt;p&gt;我们无法使用for循环，只能把循环拆成loop回调。这就是异步代码破坏了代码局部性的例证。可能有些朋友会觉得这样的代码写起来没什么困难的，那么如果再加上if…else或是try…catch呢？不管怎么样，这段代码破坏了程序员编程思路，我觉得实在太丑了。幸好，使用AsyncTask和迭代生成器之后代码完全不需要这么写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// beginMove with AsyncTask
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveAsync2 = &lt;span style="color: blue"&gt;function&lt;/span&gt;(e, startPos, endPos, duration) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;generator = &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
            e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
            e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
            &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;yield&lt;/span&gt; sleepAsync(50);&lt;/span&gt;
        }

        e.style.left = endPos.x;
        e.style.top = endPos.y;
    };

    &lt;span style="color: blue"&gt;return new &lt;/span&gt;AsyncTask(generator());
}&lt;/pre&gt;

&lt;p&gt;调用beginSleep之后代码使用yield将控制权交还给了AsyncIterator，而beginSleep完成之后也会通知AsyncIterator并继续这段逻辑。有了yield，我们的代码编写起来便顺畅多了。与之前yield只是用作“交出控制权”相比，如今的yield直接返回一个异步任务，可谓干净清爽，语义优雅。&lt;/p&gt;

&lt;h1&gt;示例二：获取多个URL的内容&lt;/h1&gt;

&lt;p&gt;如果有一个URL数组，要编写一个异步方法，返回这个URL数组对应的请求内容。如今我们可以这么写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;receiveAllAsync = &lt;span style="color: blue"&gt;function&lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;result = [];

    &lt;span style="color: blue"&gt;var &lt;/span&gt;generator = &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;req = &lt;span style="color: blue"&gt;new &lt;/span&gt;XMLHttpRequest();
            req.open(&lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;, urls[i]);
            &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;content = &lt;span style="color: blue"&gt;yield&lt;/span&gt; req.receiveAsync();&lt;/span&gt;
            result.push(content);
        }
    };

    &lt;span style="color: blue"&gt;return &lt;/span&gt;{
        start: &lt;span style="color: blue"&gt;function&lt;/span&gt;(callback) {
            (&lt;span style="color: blue"&gt;new &lt;/span&gt;AsyncTask()).start(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                callback(result);
            });
        }
    };
}&lt;/pre&gt;

&lt;p&gt;与上例有所不同的是，我们通过yield语句直接得到了receiveAsync异步任务的结果。比较可惜的一点是，无论是之前的AsyncIterator还是现在的AsyncTask都对需要返回值的异步方法不是十分友好，写法有些绕。这也是yield受到功能本身的限制，可见语言特性的确对编程模型的塑造有十分重要的影响：C# 2.0的异步编程模型不如JavaScript 1.7，JavaScript 1.7的异步编程模型却不如&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;总体而言，有了JavaScript 1.7中的迭代生成器，在许多时候已经可以极大地简化异步操作的编程体验了。只可惜JavaScript 1.7只是Mozilla一家的语言，虽然在ECMAScript 4中也有类似功能，但在ECMAScript 5却已经被废弃了。在可预见的将来，这个语言特性不会被各浏览器或是JavaScript引擎所接受。实在可以说是&lt;a href="http://en.wikipedia.org/wiki/Design_by_committee"&gt;委员会设计模式&lt;/a&gt;的又一次伟大胜利——他们就是不希望开发人员的生活能够好过一些。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/12/javascript-17-yield-async-programming.html#comments</comments>
      <pubDate>Fri, 03 Dec 2010 11:09:53 GMT</pubDate>
      <lastBuildDate>Fri, 03 Dec 2010 11:10:16 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <title>JavaScript版本的AsyncEnumerator</title>
      <link>http://blog.zhaojie.me/2010/11/asynciterator-the-asyncenumerator-in-javascript.html</link>
      <guid>http://blog.zhaojie.me/2010/11/asynciterator-the-asyncenumerator-in-javascript.html</guid>
      <description>&lt;p&gt;地球人都知道，在C# 2.0里提供了yield关键字，可以方便好用地生成一个迭代器，更可以&lt;a href="http://blog.zhaojie.me/2010/07/why-java-sucks-and-csharp-rocks-6-yield.html"&gt;简化异步操作&lt;/a&gt;——这是因为有了Jeffrey Richter开发的AsyncEnumerator。在接下来的某些演讲中我准备的主题是“异步编程模型”的演变，自然少不了这非常重要的一环。为了便于广大人民群众更好地接受，我决定使用JavaScript来进行说明。为此，我用JavaScript实现了一个AsyncEnumerator。&lt;/p&gt;

&lt;h1&gt;JavaScript 1.7里的Iterator生成器&lt;/h1&gt;

&lt;p&gt;AsyncEnumerator的关键在于yield，yield可以让开发者&lt;a href="http://blog.zhaojie.me/2010/06/code-for-fun-iterator-generator-yield-in-javascript.html"&gt;轻易实现出一个迭代器&lt;/a&gt;。只可惜在&lt;a href="http://www.ecma-international.org/publications/standards/Ecma-262.htm"&gt;ECMA-262&lt;/a&gt;里并没有定义这个功能，还好在&lt;a href="https://developer.mozilla.org/en/New_in_javascript_1.7"&gt;FireFox 2.0之后里实现了JavaScript 1.7&lt;/a&gt;，其中便提供了&lt;a href="https://developer.mozilla.org/en/JavaScript/Guide/Iterators_and_Generators"&gt;Iterator的生成器&lt;/a&gt;。这里我先做一个简单的介绍。&lt;/p&gt;

&lt;p&gt;例如，基于JavaScript 1.7实现无限长的菲波纳妾数列，只要：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;fibonacci() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;fn1 = 1;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;fn2 = 1;
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;current = fn2;
        fn2 = fn1;
        fn1 = fn1 + current;
        &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;yield&lt;/span&gt; current;&lt;/span&gt;
    }
}&lt;/pre&gt;

&lt;p&gt;在使用的时候，JavaScript 1.7里也提供了比较方便的语法，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i &lt;span style="color: blue"&gt;in &lt;/span&gt;fibonacci()) {
    print(i);
}&lt;/pre&gt;

&lt;p&gt;就像C#中的foreach是使用了MoveNext方法和Current属性一样，其实JavaScript 1.7中针对迭代器的for…in语法也是使用了迭代器对象上的next方法。例如上面的代码便可以修改为：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;iterator = fibonacci();
&lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
    print(iterator.next());
}&lt;/pre&gt;

&lt;p&gt;您可能会想，这为什么是一个死循环？这是因为在JavaScript 1.7中，而每次调用next方法都会得到yield返回的某一项，同时通过抛出“StopIteration”来表示“迭代终止”。一般来说不需要手动维护异常，因为这些都交给for…in处理了。&lt;/p&gt;

&lt;h1&gt;异步模型&lt;/h1&gt;

&lt;p&gt;无论是哪种“异步编程模型”，首先都需要总结出一种通用的“异步任务模型”，例如.NET中的Begin/End或是基于事件的异步模型。为了简化问题，我在这里提出一种最为简单的异步编程：每个异步操作都是以下形式的：&lt;/p&gt;

&lt;pre class="code"&gt;beginXxx(arg0, arg1, ..., callback);&lt;/pre&gt;

&lt;p&gt;例如我们来扩展一下XMLHttpRequest类型，提供一个beginReceive方法，最终返回responseText：&lt;/p&gt;

&lt;pre class="code"&gt;XMLHttpRequest.prototype.beginReceive = &lt;span style="color: blue"&gt;function &lt;/span&gt;(callback) {
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.onreadystatechange = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.readyState == 4) {
            callback(&lt;span style="color: blue"&gt;this&lt;/span&gt;.responseText);
        }
    }

    &lt;span style="color: blue"&gt;this&lt;/span&gt;.send();
}&lt;/pre&gt;

&lt;p&gt;同样，我们提供一个beginSleep方法，它仅仅是一个setTimeout函数的简单封装：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;beginSleep = &lt;span style="color: blue"&gt;function &lt;/span&gt;(ms, callback) {
    window.setTimeout(callback, ms);
}&lt;/pre&gt;

&lt;p&gt;调用beginXxx方法则表示发起一个异步的Xxx操作，其结果会通过callback回调函数传递过来。“回调”是“异步”的精髓，尽管它经常让我们痛苦万分。一般来说，无论是何种异步模型（甚至不关.NET还是JavaScript），最终都是基于回调函数的，最终其实也可以归纳成这里的异步调用形式。&lt;/p&gt;

&lt;h1&gt;AsyncIterator&lt;/h1&gt;

&lt;p&gt;下面我们便来仿造AsyncEnumerator编写一个AsyncIterator。AsyncEnumerator的原理很简单，我们在这里继续将其简化，由于是JavaScript，我们还不需要处理多线程之间的竞争，可谓再简单不过了：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;AsyncIterator = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._result = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._callback = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._iterator = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
}
AsyncIterator.prototype.callback = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;_this = &lt;span style="color: blue"&gt;this&lt;/span&gt;;
    &lt;span style="color: blue"&gt;return function &lt;/span&gt;(result) {
        _this._result = result;
        &lt;span style="color: blue"&gt;try &lt;/span&gt;{
            _this._iterator.next();
        } &lt;span style="color: blue"&gt;catch &lt;/span&gt;(e) {
            _this._callback();
        }
    };
}
AsyncIterator.prototype.result = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
    &lt;span style="color: blue"&gt;return this&lt;/span&gt;._result;
}
AsyncIterator.prototype.beginInvoke = &lt;span style="color: blue"&gt;function &lt;/span&gt;(iterator, callback) {
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._iterator = iterator;
    &lt;span style="color: blue"&gt;this&lt;/span&gt;._callback = callback;

    iterator.next();
}&lt;/pre&gt;

&lt;p&gt;AsyncIterator的原理和使用方式可以用以下几句话描述清楚：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;异步代码将异步操作的结果提交至AsyncIterator提供的回调函数中（通过调用callback方法获得）。 &lt;/li&gt;

  &lt;li&gt;异步代码在发起一个异步操作之后，使用yield将控制权交还给外部（其实就是AsyncIterator）。 &lt;/li&gt;

  &lt;li&gt;异步操作完成后会执行AsyncIterator提供的回调函数，AsyncIterator则调用迭代器的next方法，继续执行代码。 &lt;/li&gt;

  &lt;li&gt;异步代码可以从AsyncIterator的result方法中获得上一个异步操作的结果。 &lt;/li&gt;

  &lt;li&gt;当next方法抛出异常时，表示迭代器执行完毕，AsyncIterator则通过回调函数进行通知。 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为了便于使用，我为AsyncIterator提供一个“静态方法”：&lt;/p&gt;

&lt;pre class="code"&gt;AsyncIterator.beginInvoke = &lt;span style="color: blue"&gt;function &lt;/span&gt;(generator, callback) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;ai = &lt;span style="color: blue"&gt;new &lt;/span&gt;AsyncIterator();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;iterator = generator(ai);
    ai.beginInvoke(iterator, callback);
}&lt;/pre&gt;

&lt;p&gt;接下来，我们便来看两个实例。&lt;/p&gt;

&lt;h1&gt;示例1：移动HTML元素&lt;/h1&gt;

&lt;p&gt;实现一个移动HTML元素的逻辑其实很简单，只要根据一个时间间隔不断地改变其top和left即可。例如这样：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// Pseudocode, cannot work
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;move = &lt;span style="color: blue"&gt;function &lt;/span&gt;(e, startPos, endPos, duration) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
        e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
        e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
        sleep(50); &lt;span style="color: #006400"&gt;// cannot sleep
    &lt;/span&gt;}

    e.style.left = endPos.x;
    e.style.top = endPos.y;
}&lt;/pre&gt;

&lt;p&gt;只可惜上面这段代码是无法运行的，因为在浏览器里我们没有任何手段让当前的工作线程暂停，我们没有一个阻塞的同步的sleep方法，只有一个异步的beginSleep方法（如上文）。因此，其实beginMove方法只能这样编写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;beginMove = &lt;span style="color: blue"&gt;function &lt;/span&gt;(e, startPos, endPos, duration, callback) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;loop = &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(t &amp;lt; duration) {
            t += 50;
            e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
            e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
            beginSleep(50, loop);
        } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
            callback();
        }
    }

    loop();
}&lt;/pre&gt;

&lt;p&gt;我们无法使用for循环，只能把循环拆成loop回调。这就是异步代码破坏了代码局部性的例证。可能有些朋友会觉得这样的代码写起来没什么困难的，那么如果再加上if…else或是try…catch呢？不管怎么样，这段代码破坏了程序员编程思路，我觉得实在太丑了，使用AsyncIterator便会直观许多：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;beginMove2 = &lt;span style="color: blue"&gt;function &lt;/span&gt;(e, startPos, endPos, duration, callback) {

    &lt;span style="color: blue"&gt;var &lt;/span&gt;generator = &lt;span style="color: blue"&gt;function &lt;/span&gt;(ai) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
            e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
            e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
            beginSleep(50, &lt;span style="background-color: yellow"&gt;ai.callback()&lt;/span&gt;);
            &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;yield &lt;/span&gt;0;&lt;/span&gt;
        }

        e.style.left = endPos.x;
        e.style.top = endPos.y;
    };

    AsyncIterator.beginInvoke(generator, callback);
}&lt;/pre&gt;

&lt;p&gt;调用beginSleep之后代码使用yield将控制权交还给了AsyncIterator，而beginSleep完成之后也会通知AsyncIterator并继续这段逻辑。有了yield，我们的代码编写起来便顺畅多了。&lt;/p&gt;

&lt;h1&gt;示例2：批量请求数据&lt;/h1&gt;

&lt;p&gt;如果现在有一个urls数组，其中包含了目标地址，您如何将它们的结果也通过一个数组返回过来呢？伪代码如下：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: #006400"&gt;// Pseudocode, cannot work
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;receiveMany = &lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;results = [];

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;req = &lt;span style="color: blue"&gt;new &lt;/span&gt;XMLHttpRequest();
        req.open(&lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;, urls[i]);
        &lt;span style="color: blue"&gt;var &lt;/span&gt;r = req.receive(); &lt;span style="color: #006400"&gt;// cannot recieve
        &lt;/span&gt;results.push(r);
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;results;
}&lt;/pre&gt;

&lt;p&gt;很显然实际可以运行的代码只能是异步的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;beginReceiveMany = &lt;span style="color: blue"&gt;function &lt;/span&gt;(urls, callback) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;results = [];

    &lt;span style="color: blue"&gt;var &lt;/span&gt;loop = &lt;span style="color: blue"&gt;function &lt;/span&gt;(i) {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(i &amp;lt; urls.length) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;req = &lt;span style="color: blue"&gt;new &lt;/span&gt;XMLHttpRequest();
            req.open(&lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;, urls[i]);
            req.beginReceive(&lt;span style="color: blue"&gt;function &lt;/span&gt;(r) {
                results.push(r);
                loop(i + 1);
            });
        } &lt;span style="color: blue"&gt;else &lt;/span&gt;{
            callback(results);
        }
    }

    loop(0);
}&lt;/pre&gt;

&lt;p&gt;之前我们扩展了XMLHttpRequest，提供了一个beginReceive方法。于是我们在回调中驰骋，最终实现了beginReceiveMany方法。如果用AsyncIterator则会方便一些，只可惜它对“带有返回值”的异步操作支持并不友好，因此还是有点绕：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;beginReceiveMany2 = &lt;span style="color: blue"&gt;function &lt;/span&gt;(urls, callback) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;results = [];

    &lt;span style="color: blue"&gt;var &lt;/span&gt;generator = &lt;span style="color: blue"&gt;function&lt;/span&gt;(ai) {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;req = &lt;span style="color: blue"&gt;new &lt;/span&gt;XMLHttpRequest();
            req.open(&lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;, urls[i]);
            req.beginReceive(&lt;span style="background-color: yellow"&gt;ai.callback()&lt;/span&gt;);
            yield 0;
            results.push(&lt;span style="background-color: yellow"&gt;ai.result()&lt;/span&gt;);
        }
    }
    
    AsyncIterator.beginInvoke(generator, &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
        callback(results);
    });
}&lt;/pre&gt;

&lt;p&gt;幸好有JavaScript的闭包在，总体来说还算方便。在这个例子中，AsyncIterator会得到beginReceive从回调函数中提交上来的结果，然后代码便可以从result方法里获得并保存。&lt;/p&gt;

&lt;h1&gt;更多&lt;/h1&gt;

&lt;p&gt;AsyncIterator虽好，只可惜只能在FireFox里使用，这自然无法推广。幸好我们还可以用&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;，而且这一切都没有Jscex来的简单，正如之前所提到的那样，在Jscex中一段移动HTML元素的动画只需这样写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(e, startPos, endPos, duration) {
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;t = 0; t &amp;lt; duration; t += 50) {
        e.style.left = startPos.x + (endPos.x - startPos.x) * t / duration;
        e.style.top = startPos.y + (endPos.y - startPos.y) * t / duration;
        &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(Jscex.Async.sleep(50));
    }

    e.style.left = endPos.x;
    e.style.top = endPos.y;
}));&lt;/pre&gt;

&lt;p&gt;批量请求数据也是最为直观的代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;getMultiContentAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(urls) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;result = [];

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;i = 0; i &amp;lt; urls.length; i++) {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;content = &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(getContentAsync(urls[i]));
        result.push(content);
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;result;
}));&lt;/pre&gt;

&lt;p&gt;甚至&lt;a href="http://files.zhaojie.me/demos/jscex-hanoi/hanoi.html?n=5"&gt;汉诺塔&lt;/a&gt;的解法，也完全是最最直观的递归算法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;hanoiAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(n, a, b, c) {
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(n &amp;gt; 0) {
        &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(hanoiAsync(n - 1, a, c, b));
    }

    &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(moveDishAsync(n, a, b));

    &lt;span style="color: blue"&gt;if &lt;/span&gt;(n &amp;gt; 0) {
        &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(hanoiAsync(n - 1, c, b, a));
    }
}));&lt;/pre&gt;

&lt;p&gt;关于异步编程模型的演变，以及Jscex的更多内幕，本周三下午4点我将在创新院内部开展一次分享会，&lt;span style="color:red;"&gt;欢迎创新院外的朋友前来一起交流&lt;/span&gt;。这也是我在即将举办的&lt;a href="http://sd2china.csdn.net/"&gt;SD 2.0&lt;/a&gt;大会上的一次预演。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/11/asynciterator-the-asyncenumerator-in-javascript.html#comments</comments>
      <pubDate>Mon, 29 Nov 2010 14:25:50 GMT</pubDate>
      <lastBuildDate>Mon, 29 Nov 2010 17:18:39 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/news/">新闻信息</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>关于即将到来的“演出季”以及Jscex类库</title>
      <link>http://blog.zhaojie.me/2010/11/the-coming-talks-and-jscex.html</link>
      <guid>http://blog.zhaojie.me/2010/11/the-coming-talks-and-jscex.html</guid>
      <description>&lt;p&gt;又到了一年一度的“演出季”，接下来将是各式会议扑面而来的一个月。作为“与会爱好者”我自然也进入了繁忙的准备工作。接下来我将在&lt;a href="http://tup.csdn.net/"&gt;TUP&lt;/a&gt;（11月27日）、&lt;a href="http://www.net-china.org/"&gt;2010年第二届.NET技术大会&lt;/a&gt;（12月4~5日）以及&lt;a href="http://sd2china.csdn.net/"&gt;CSDN软件开发2.0大会&lt;/a&gt;（12月9~10日）上与大家分享四场演讲。不过除了一场是关于Windows并发编程的基础以外，其余三场的话题都是围绕“微软在异步编程方面的演变”。在这场演讲中，我还会引入一个与该话题密切相关的JavaScript类库：Jscex。&lt;/p&gt;

&lt;p&gt;并发编程已经成为我们无法回避的一点，但是传统的编程语言在并发、异步等方面的支持非常有限，除了一些面向并发的编程语言（如Erlang），我们只能使用回调的方式，这样代码则被拆得支离破碎。如今的一些新语言，如F#，Scala，Go以及未来的C#都会在这方面有直接的支持。就我个人而言，用过了这些语言之后，就几乎无法回头，因为我实在无法体会到编程的快感。&lt;/p&gt;

&lt;p&gt;如今微软的.NET平台在异步编程领域已经开始发力，从最初十分原始的Begin/End的异步模型和基于事件的异步模型，到后来&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html"&gt;F#中的异步工作流&lt;/a&gt;，.NET 4.0中的&lt;a href="http://blog.zhaojie.me/2010/09/async-programming-and-reactive-framework.html"&gt;Observable及响应式框架&lt;/a&gt;，以及&lt;a href="http://blog.zhaojie.me/2010/10/pdc2010-the-future-of-csharp-and-vb-by-anders-hejlsberg-1.html"&gt;未来C#中将要出现的异步特性&lt;/a&gt;，.NET平台上涌现了各种与并发和异步编程的机制。未来几场演讲的主要话题，便是关于这些异步编程模型的演变。&lt;/p&gt;

&lt;p&gt;只可惜，在某些和异步密切相关的平台上，例如使用JavaScript的浏览器平台，开发人员还是不得不利用最传统的开发模型。响应式框架通过引入一个“推模型”改进了这一点，不过我最欣赏的异步编程支持还是F#上的异步工作流。首先，它是一套“类库”（基于F#中的计算表达式特性）；其次，它让异步编程变得像传统的顺序式开发那样简单。有天我突然意识到，JavaScript其实提供一个很强大的机制：它可以使用toString方法得到一个函数的原始代码，于是我们可以对此解析并生成新的代码，最后使用eval得到新的函数。通过这个手法，JavaScript语言几乎得到了无限的灵活性。&lt;/p&gt;

&lt;p&gt;于是在未来的演讲中，我将会展示一个JavaScript类库的原型：&lt;a href="https://github.com/JeffreyZhao/jscex"&gt;Jscex&lt;/a&gt;（目前还未提交任何代码）。Jscex是&lt;font color="#ff0000"&gt;J&lt;/font&gt;ava&lt;font color="#ff0000"&gt;S&lt;/font&gt;cript &lt;font color="#ff0000"&gt;C&lt;/font&gt;omputation &lt;font color="#ff0000"&gt;Ex&lt;/font&gt;pressions的缩写，即“JavaScript的计算表达式”。计算表达式的原理，便是将一段顺序式的代码，重新编译成另外一个表达式，它便是F#中异步工作流的基础。&lt;/p&gt;

&lt;p&gt;例如，它会将下面这段JavaScript函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(urlA, urlB) {
    &lt;span style="color: blue"&gt;var &lt;/span&gt;reqA = &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(Jscex.Async.sendRequest(urlA, &lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;));
    &lt;span style="color: blue"&gt;var &lt;/span&gt;lengthA = reqA.responseText.length;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;reqB = &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(Jscex.Async.sendRequest(urlB, &lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;));
    &lt;span style="color: blue"&gt;var &lt;/span&gt;lengthB = reqB.responseText.length;

    &lt;span style="color: blue"&gt;return &lt;/span&gt;lengthA + lengthB;
}&lt;/pre&gt;

&lt;p&gt;编译成另一段代码：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function&lt;/span&gt; (urlA, urlB) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(Jscex.Async.sendRequest(urlA, &lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;), &lt;span style="color: blue"&gt;function&lt;/span&gt;(reqA) {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;lengthA = reqA.responseText.length;
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(Jscex.Async.sendRequest(urlB, &lt;span style="color: maroon"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt;), &lt;span style="color: blue"&gt;function&lt;/span&gt;(reqB) {
                &lt;span style="color: blue"&gt;var &lt;/span&gt;lengthB = reqB.responseText.length;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return(lengthA + lengthB);
            });
        });
    });
};&lt;/pre&gt;

&lt;p&gt;对于$async这个构造器来说，$await便是一个bind函数，在此处则会形成一个回调。此外，Jscex也必须能够正确处理循环操作，例如这个&lt;a href="http://files.zhaojie.me/demos/jscex-prototype/clock.html"&gt;时钟示例&lt;/a&gt;（建议使用IE 9，Chrome或Firefox等支持canvas的浏览器观看）其实是这样实现的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;drawClockAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(interval) {
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;) {
        drawClock(&lt;span style="color: blue"&gt;new &lt;/span&gt;Date());
        &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(Jscex.Async.sleep(interval));
    }
}));

Jscex.Async.start(drawClockAsync(1000));&lt;/pre&gt;

&lt;p&gt;对于时钟来说，我们往往会不断使用window.setTimeout来更新界面。不过有了Jscex，一切都变得“顺其自然”。我们只要写一个“死循环”，在需要“等待”的时候，直接$await一个sleep操作即可。这种看似“阻塞”的代码，其实最终会被重新编译成新的函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function&lt;/span&gt;(interval) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.While(
            &lt;span style="color: blue"&gt;function&lt;/span&gt;() { &lt;span style="color: blue"&gt;return true&lt;/span&gt;; },
            $async.Delay(&lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                drawClock(&lt;span style="color: blue"&gt;new &lt;/span&gt;Date);
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(Jscex.Async.sleep(interval), &lt;span style="color: blue"&gt;function&lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
                });
            })
        );
    });
};&lt;/pre&gt;

&lt;p&gt;实际上，它们都是异步的。&lt;/p&gt;

&lt;p&gt;其实在浏览器上有太多的异步场景。例如一个AJAX请求，或者是一段动画效果。这里我还准备了&lt;a href="http://files.zhaojie.me/demos/jscex-prototype/animation.html"&gt;第二个示例&lt;/a&gt;，其中有一段移动元素的方法（将元素e在duration时间内从startPos移动至endPos）是这样编写的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(e, startPos, endPos, duration) {
    e.style.left = startPos.x;
    e.style.top = startPos.y;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;time = 0;
    &lt;span style="color: blue"&gt;while &lt;/span&gt;(time &amp;lt; duration) {
        &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(Jscex.Async.sleep(50));
        time = time + 50;
        e.style.left = startPos.x + (endPos.x - startPos.x) * time / duration;
        e.style.top = startPos.y + (endPos.y - startPos.y) * time / duration;
    }
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;moveBox = document.getElementById(&lt;span style="color: maroon"&gt;&amp;quot;moveBox&amp;quot;&lt;/span&gt;);
Jscex.Async.start(moveAsync(moveBox, { x: 0, y: 0 }, { x: 300, y: 0 }, 1000));&lt;/pre&gt;

&lt;p&gt;没有回调，只有最普通的顺序式编程。甚至我们还可以将moveAsync方法组合到另一个方法中去：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;moveSquareAsync = eval(Jscex.compile(&lt;span style="color: maroon"&gt;&amp;quot;$async&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;function &lt;/span&gt;(e) {
    &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(moveAsync(e, { x: 100, y: 100 }, { x: 400, y: 100 }, 1000));
    &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(moveAsync(e, { x: 400, y: 100 }, { x: 400, y: 400 }, 1000));
    &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(moveAsync(e, { x: 400, y: 400 }, { x: 100, y: 400 }, 1000));
    &lt;font color="#ff0000"&gt;$await&lt;/font&gt;(moveAsync(e, { x: 100, y: 400 }, { x: 100, y: 100 }, 1000));
}));

&lt;span style="color: blue"&gt;var &lt;/span&gt;moveSquareBox = document.getElementById(&lt;span style="color: maroon"&gt;&amp;quot;moveSquareBox&amp;quot;&lt;/span&gt;);
Jscex.Async.start(moveSquareAsync(moveSquareBox));&lt;/pre&gt;

&lt;p&gt;如何让一个元素沿方形移动？“连续执行”四遍moveAsync方法即可。当然，moveSquareAsync方法最终还是由“回调”组成的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;function &lt;/span&gt;(e) {
    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Delay(&lt;span style="color: blue"&gt;function &lt;/span&gt;() {
        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(moveAsync(e, {...}, {...}, 1000), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
            &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(moveAsync(e, {...}, {...}, 1000), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(moveAsync(e, {...}, {...}, 1000), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                    &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Bind(moveAsync(e, {...}, {...}, 1000), &lt;span style="color: blue"&gt;function &lt;/span&gt;() {
                        &lt;span style="color: blue"&gt;return &lt;/span&gt;$async.Return();
                    });
                });
            });
        });
    });
};&lt;/pre&gt;

&lt;p&gt;我以前在许多地方说过，目前浏览器上的开发效率已经受限于JavaScript的语言特性了。不过，正如我刚才所提到的那样，JavaScript语言的灵活性几乎是无穷的。我认为，Jscex会是一个突破。当然，现在您看到的演示只不过是个原型，如果要提供一个完整的解决方案还有太多的路要走。例如现在的Jscex还不支持for，if，try...catch等常见语言特性，代码也丑陋得很。与此对应的Jscex.Async也只是个初步的模型，且只有寥寥无几的辅助方法。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/11/the-coming-talks-and-jscex.html#comments</comments>
      <pubDate>Mon, 22 Nov 2010 12:30:57 GMT</pubDate>
      <lastBuildDate>Mon, 29 Nov 2010 14:28:28 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载</title>
      <link>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html</link>
      <guid>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html</guid>
      <description>&lt;p&gt;经过一个多星期的努力，我们在此为大家奉上&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;第二届.NET技术交流会&lt;/a&gt;的演讲录像。由于录像过程中的一些失误，我们在在讲师录像方面存在着很大问题，经过补救，也只能得到后两场演讲中使用手持设备拍摄下来的录像。在此向大家表示深深的歉意，有了这次的教训，我们以后会更加重视每一个环节的预防及补救措施，尽力避免如现在这样无可挽回的结果。&lt;/p&gt;

&lt;h1&gt;响应式编程与响应式框架&lt;/h1&gt;

&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;简介：异步编程改变了我们的编程方式，也为我们带来的许多挑战，同时让一些编程模型重新焕发了生机。与传统的“拉”模型不同，响应式编程将异步事件流视为可观察的集合，这是一种“推”模型。微软为了提高云时代的编程体验而设计了响应式框架，其目的是为了简化复杂事件处理之间混合操作。从中我们了解到一些异步编程的模式与LINQ使用技巧，并可以将这种编程模型普及到JavaScript等其他平台上去。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjA3OTIzNzI0/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/rx.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，265M）&lt;/p&gt;

&lt;h1&gt;大话程序员可用的算法&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：程劭非，盛大创新院，研究员。网名winter，无忧脚本版主。Web前端技术的积极倡导者。学生时代曾经热衷于参加ACM/ICPC。目前工作在Bambook电子书项目，主要负责文字排版和浏览器引擎WebKit相关。之前曾负责在Windows CE系统上的IE开发。&lt;/p&gt;

&lt;p&gt;简介：俗话说“数据结构+算法=程序”，算法是什么？算法书里满篇是看不懂的形式化推导，网上一些&amp;quot;高人&amp;quot;写的关于算法文章高深莫测，大公司面 试最让人讨厌的就是考算法题，“我做了这么多年，跟本在实际开发中就没用过算法！”，算法真的是距离我们如此遥远的东西吗？且听这回演讲， 算法究竟如何影响我们的开发。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjA3OTI3MjAw/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/algorithms.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，128M）&lt;/p&gt;

&lt;h1&gt;Windows内核技术介绍&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：潘爱民，盛大创新院专家，微软学者，集团COO专家顾问。长期从事软件和系统技术的研究和开发工作，撰写了大量软件技术文章，并著译了多部经典计算机图书。在MSR/清华等从事多年科研工作，在北大和清华多年执教经验。数学学士学位和计算机科学博士，主要研究领域包括软件设计、信息安全、操作系统和Internet技术。&lt;/p&gt;

&lt;p&gt;简介：Windows操作系统经过二十年的发展，已臻成熟。Microsoft在推动Windows内核方面做了大量工作，譬如于2006年夏季向教育界开放了当时最为先进的内核源代码（Windows Research Kernel）。主讲者在这次讲座中，结合这些可利用的资源，分享对Windows内核研究的体会，尤其将重点讨论Windows中的I/O模型和环境子系统。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjEyOTkyMzgw/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/win-kernal.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，480M）&lt;/p&gt;

&lt;h1&gt;面向对象与生活&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：高翔，5173.com&amp;#160; 项目经理。关注前沿技术和技术人员的非技术生活。对面向对象、模式和建模技术有浓厚兴趣，并对游戏设计和图形学方面也比较感兴趣。最近在学习F#，Lua以及关注一些关于职业生涯规划方面的话题。&lt;/p&gt;

&lt;p&gt;简介：面向对象这个话题虽然很热，但与哲学一样，很难给其一个很准确的定义。也因为如此，每个人对它都有自己的理解。本次演讲将从一个实际的例子出发，逐步引入面向对象的三个特征，结合对象的生命周期，以及基于事件的对象扩展方式等方面，探讨其与设计模式，与生活之间的联系。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjA2NjIxMzky/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100928/1/ooad.mov"&gt;高清格式下载&lt;/a&gt;（mov格式，1280 * 720，320M）&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载 &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html#comments</comments>
      <pubDate>Tue, 21 Sep 2010 03:27:11 GMT</pubDate>
      <lastBuildDate>Tue, 21 Sep 2010 03:27:11 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片</title>
      <link>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html</link>
      <guid>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html</guid>
      <description>&lt;p&gt;昨天有160多位朋友参加了&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;第二届.NET技术交流会&lt;/a&gt;，再次感谢各位对我们的支持。比较遗憾的是，这次的讲师录像方面有着很大问题，我们正在想办法进行修补，希望可以有“差强人意”的结果。现在，大家请在第一时间浏览本次活动新鲜出炉的幻灯片。&lt;/p&gt;

&lt;h1&gt;响应式编程与响应式框架&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;简介：异步编程改变了我们的编程方式，也为我们带来的许多挑战，同时让一些编程模型重新焕发了生机。与传统的“拉”模型不同，响应式编程将异步事件流视为可观察的集合，这是一种“推”模型。微软为了提高云时代的编程体验而设计了响应式框架，其目的是为了简化复杂事件处理之间混合操作。从中我们了解到一些异步编程的模式与LINQ使用技巧，并可以将这种编程模型普及到JavaScript等其他平台上去。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_5183111"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="响应式编程及框架" href="http://www.slideshare.net/jeffz/reactive-programming-and-framework"&gt;响应式编程及框架&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183111" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=rx-100912025913-phpapp01&amp;stripped_title=reactive-programming-and-framework" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183111" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=rx-100912025913-phpapp01&amp;stripped_title=reactive-programming-and-framework" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/rx-zhaojie.pdf"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/rx-demo-zhaojie.zip"&gt;示例程序&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;大话程序员可用的算法&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：程劭非，盛大创新院，研究员。网名winter，无忧脚本版主。Web前端技术的积极倡导者。学生时代曾经热衷于参加ACM/ICPC。目前工作在Bambook电子书项目，主要负责文字排版和浏览器引擎WebKit相关。之前曾负责在Windows CE系统上的IE开发。&lt;/p&gt;

&lt;p&gt;简介：俗话说“数据结构+算法=程序”，算法是什么？算法书里满篇是看不懂的形式化推导，网上一些&amp;quot;高人&amp;quot;写的关于算法文章高深莫测，大公司面 试最让人讨厌的就是考算法题，“我做了这么多年，跟本在实际开发中就没用过算法！”，算法真的是距离我们如此遥远的东西吗？且听这回演讲， 算法究竟如何影响我们的开发。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_5183325"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="大话程序员可用的算法" href="http://www.slideshare.net/jeffz/programmers-and-algorithms"&gt;大话程序员可用的算法&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183325" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912045100-phpapp01&amp;stripped_title=programmers-and-algorithms" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183325" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912045100-phpapp01&amp;stripped_title=programmers-and-algorithms" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/algorithms-chengshaofei.pdf"&gt;幻灯片&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;Windows内核技术介绍&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：潘爱民，盛大创新院专家，微软学者，集团COO专家顾问。长期从事软件和系统技术的研究和开发工作，撰写了大量软件技术文章，并著译了多部经典计算机图书。在MSR/清华等从事多年科研工作，在北大和清华多年执教经验。数学学士学位和计算机科学博士，主要研究领域包括软件设计、信息安全、操作系统和Internet技术。&lt;/p&gt;

&lt;p&gt;简介：Windows操作系统经过二十年的发展，已臻成熟。Microsoft在推动Windows内核方面做了大量工作，譬如于2006年夏季向教育界开放了当时最为先进的内核源代码（Windows Research Kernel）。主讲者在这次讲座中，结合这些可利用的资源，分享对Windows内核研究的体会，尤其将重点讨论Windows中的I/O模型和环境子系统。&lt;/p&gt;

&lt;div style="width:425px" id="__ss_5183269"&gt;&lt;strong style="display:block;margin:12px 0 4px"&gt;&lt;a href="http://www.slideshare.net/jeffz/win-os-kernel-tech" title="Windows内核技术介绍"&gt;Windows内核技术介绍&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183269" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=winos-kernel-techforsnda2010-9-100912042602-phpapp01&amp;stripped_title=win-os-kernel-tech&amp;userName=jeffz" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183269" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=winos-kernel-techforsnda2010-9-100912042602-phpapp01&amp;stripped_title=win-os-kernel-tech&amp;userName=jeffz" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div style="padding:5px 0 12px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/win-kernel-panaimin.pdf"&gt;幻灯片&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;面向对象与生活&lt;/h1&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;p&gt;讲师：高翔，5173.com&amp;#160; 项目经理。关注前沿技术和技术人员的非技术生活。对面向对象、模式和建模技术有浓厚兴趣，并对游戏设计和图形学方面也比较感兴趣。最近在学习F#，Lua以及关注一些关于职业生涯规划方面的话题。&lt;/p&gt;

&lt;p&gt;简介：面向对象这个话题虽然很热，但与哲学一样，很难给其一个很准确的定义。也因为如此，每个人对它都有自己的理解。本次演讲将从一个实际的例子出发，逐步引入面向对象的三个特征，结合对象的生命周期，以及基于事件的对象扩展方式等方面，探讨其与设计模式，与生活之间的联系。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_5183310"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="面向对象与生活" href="http://www.slideshare.net/jeffz/object-orientation-and-life"&gt;面向对象与生活&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse5183310" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912044502-phpapp01&amp;stripped_title=object-orientation-and-life" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse5183310" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100912044502-phpapp01&amp;stripped_title=object-orientation-and-life" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;相关下载：&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/ooad-gaoxiang.pdf"&gt;幻灯片&lt;/a&gt;、附加资料“&lt;a href="http://files.zhaojie.me/slides/snda-dotnet-conf/20100911/%e4%b8%96%e7%95%8c%e7%9a%84%e5%ae%8f%e8%a7%82%e5%92%8c%e5%be%ae%e8%a7%82.pptx"&gt;世界的宏观和微观&lt;/a&gt;”&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片 &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html#comments</comments>
      <pubDate>Sun, 12 Sep 2010 14:32:31 GMT</pubDate>
      <lastBuildDate>Sun, 19 Dec 2010 17:32:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会即将召开</title>
      <link>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html</link>
      <guid>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html</guid>
      <description>&lt;p&gt;由&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的第二届.NET技术大会将于9月11号下午1点召开，本次交流会请到了四位讲师，议题覆盖了响应式编程、算法、面向对象设计及Windows内核等多个方面，其中最为突出的莫过于由潘爱民老师为大家带来的Windows内核方面的话题。我已经看过了各场演讲的幻灯片终稿，也很期待各位讲师在正式演讲中的表现。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-2-600x850.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-2-600x850.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;本次大会中，我们还获得了人民邮电出版社&lt;a href="http://www.turingbook.com/Homepage/Default.aspx"&gt;图灵教育&lt;/a&gt;赠送的15册图书、数枚书签、几件T恤，再加上由会务经费购买的两本潘老师的著作，将会作为各场次的奖品，赠送给在交流会中表现积极的听众。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;同样，我们还请到了&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;的专业摄影师对演讲过程进行全程拍摄，并配合各位讲师自身的屏幕录像，将在后期合成为适合独立观看的演讲视频（&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;效果&lt;/a&gt;），让不能到场的朋友在线或是下载后观看。&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/ku6-logo.jpg" /&gt; 

&lt;p&gt;最后，本次活动中我们还邀请了&lt;a href="http://renjian.com/"&gt;人间网&lt;/a&gt;为我们进行线上直播，我们会有专人负责直播信息的管理和发布。您也可以在活动现场，甚至在场外通过会场的无线网络或者短信发布消息。这些信息都会显示在会场的大屏幕上。&lt;/p&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/renjian-logo.jpg" /&gt; 

&lt;p&gt;以下是直播效果：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/renjian-live-demo.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/renjian-live-demo.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;本次会议的邀请函已经发给各位报名者，请携带邀请函至会议现场签到，没有报名的朋友可以在现场直接报名。&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;关于会议的时间、地点、交通、议程等更多信息，请关注会议的报名信息&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html#comments</comments>
      <pubDate>Thu, 09 Sep 2010 03:14:43 GMT</pubDate>
      <lastBuildDate>Thu, 09 Sep 2010 03:14:43 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助第二届.NET技术交流会开始报名了！</title>
      <link>http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html</link>
      <guid>http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html</guid>
      <description>&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-in.png" /&gt; 

&lt;p&gt;自上次&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;首届.NET技术交流会&lt;/a&gt;到现在已经有两个月，这意味着按照原来的“一季一次”的计划也已经离第二次的活动不远了，考虑到9月份的中秋和国庆假期将工作日和休息日搞的支离破碎，于是交流会的时间会略微有些提前。第二届交流会的形式与上次相同，将为您献上四场高质量的技术演讲。当然这次在内容上有了新的尝试，除了引入了算法及面向对象设计的内容之外，这次更是请到了“传说中的大侠”为大家带来有关Windows内核的深度内容。&lt;font color="#ff0000"&gt;人数暂定为200人，事不宜迟，赶快报名吧&lt;/font&gt;。除了.NET社区的群众以外，也欢迎其他技术社区的朋友前来参与交流。事实上，我组织技术交流会的目的之一便是希望能够促进.NET社区与其他技术社区的交流及相互学习。&lt;/p&gt;

&lt;h1&gt;时间及议程安排&lt;/h1&gt;

&lt;p&gt;第二次交流会定于&lt;strong style="color: red"&gt;2010年9月11日&lt;/strong&gt;（周六）举行，具体时间及议程安排如下。本次依然安排了四场演讲，不过在内容上有了新的尝试：在与一些朋友和同事进行沟通之后，我决定在第二次交流会上引入与算法相关的议题，由创新院内部的ACM/ICPC达人来谈一下日常工作中的算法，如果您对于算法在工作的处境所有疑惑的话，这也是个共同探讨的好机会；此外，我也邀请了&lt;a href="http://www.5173.com/"&gt;5173.com&lt;/a&gt;的技术专家来讨论面向对象设计方面的问题，我看过他过去在内部演讲时使用的PPT，内容很充实；而这次的“重头戏”，便是请到了传说中的&lt;font color="#ff0000"&gt;潘爱民&lt;/font&gt;老师为大家演讲Windows内核方面的问题。潘老师是业界著名技术专家，不久前加入了盛大创新院，可谓“镇院之宝”。事实上，在9月初潘老师便会回到北京工作，而这次他也是为了交流会专门出差至上海，您怎能错过这次机会？&lt;/p&gt;

&lt;table style="text-align: center" border="1" cellspacing="0" cellpadding="5"&gt;&lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;时间&lt;/th&gt;

      &lt;th&gt;议程&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;&lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;12:30 ~ 13:00&lt;/td&gt;

      &lt;td&gt;签到&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;13:00 ~ 14:00&lt;/td&gt;

      &lt;td&gt;响应式编程与响应式框架&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:00 ~ 14:10&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:10 ~ 15:10&lt;/td&gt;

      &lt;td&gt;大话程序员可用的算法&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:10 ~ 15:40&lt;/td&gt;

      &lt;td&gt;茶歇&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:40 ~ 16:40&lt;/td&gt;

      &lt;td&gt;面向对象与生活&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:40 ~ 16:50&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:50 ~ 17: 50&lt;/td&gt;

      &lt;td&gt;Windows内核技术介绍&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;

&lt;h1&gt;演讲内容&lt;/h1&gt;

&lt;p&gt;以下是关于四场演讲的详细描述。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;响应式编程与响应式框架&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;异步编程改变了我们的编程方式，也为我们带来的许多挑战，同时让一些编程模型重新焕发了生机。与传统的“拉”模型不同，响应式编程将异步事件流视为可观察的集合，这是一种“推”模型。微软为了提高云时代的编程体验而设计了响应式框架，其目的是为了简化复杂事件处理之间混合操作。从中我们了解到一些异步编程的模式与LINQ使用技巧，并可以将这种编程模型普及到JavaScript等其他平台上去。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chengshaofei.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;大话程序员可用的算法&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;程劭非，盛大创新院，研究员。网名winter，无忧脚本版主。Web前端技术的积极倡导者。学生时代曾经热衷于参加ACM/ICPC。目前工作在Bambook电子书项目，主要负责文字排版和浏览器引擎WebKit相关。之前曾负责在Windows CE系统上的IE开发。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;俗话说“数据结构+算法=程序”，算法是什么？算法书里满篇是看不懂的形式化推导，网上一些&amp;quot;高人&amp;quot;写的关于算法文章高深莫测，大公司面 试最让人讨厌的就是考算法题，“我做了这么多年，跟本在实际开发中就没用过算法！”，算法真的是距离我们如此遥远的东西吗？且听这回演讲， 算法究竟如何影响我们的开发。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/gaoxiang.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;面向对象与生活&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;高翔，5173.com&amp;#160; 项目经理。关注前沿技术和技术人员的非技术生活。对面向对象、模式和建模技术有浓厚兴趣，并对游戏设计和图形学方面也比较感兴趣。最近在学习F#，Lua以及关注一些关于职业生涯规划方面的话题。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;面向对象这个话题虽然很热，但与哲学一样，很难给其一个很准确的定义。也因为如此，每个人对它都有自己的理解。本次演讲将从一个实际的例子出发，逐步引入面向对象的三个特征，结合对象的生命周期，以及基于事件的对象扩展方式等方面，探讨其与设计模式，与生活之间的联系。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/panaimin.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;Windows内核技术介绍&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;潘爱民，盛大创新院专家，微软学者，集团COO专家顾问。长期从事软件和系统技术的研究和开发工作，撰写了大量软件技术文章，并著译了多部经典计算机图书。在MSR/清华等从事多年科研工作，在北大和清华多年执教经验。数学学士学位和计算机科学博士，主要研究领域包括软件设计、信息安全、操作系统和Internet技术。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;Windows操作系统经过二十年的发展，已臻成熟。Microsoft在推动Windows内核方面做了大量工作，譬如于2006年夏季向教育界开放了当时最为先进的内核源代码（Windows Research Kernel）。主讲者在这次讲座中，结合这些可利用的资源，分享对Windows内核研究的体会，尤其将重点讨论Windows中的I/O模型和环境子系统。&lt;/p&gt;

&lt;h1&gt;地点&lt;/h1&gt;

&lt;p&gt;本次交流会举办地为&lt;strong style="color: red"&gt;上海市浦东新区碧波路888号畅星大厦&lt;/strong&gt;（地铁二号线张江高科站下，步行10分钟可达）3楼会议厅，地图如下：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;鸟瞰图：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;畅星大厦外观：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场实景照片：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场容量可以容纳超过200人，希望到时候不会显得太过空旷。:)&lt;/p&gt;

&lt;h1&gt;报名信息&lt;/h1&gt;
&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/sign-up-now.jpg" /&gt; 

&lt;p&gt;本次交流会&lt;a href="http://www.diaochapai.com/survey504720"&gt;现已开始报名，请填写报名表&lt;/a&gt;，&lt;strike&gt;报名截止日期为2010年9月5日&lt;/strike&gt;人数已满，多谢大家支持。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;盛大创新院赞助第二届.NET技术交流会开始报名了！ &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助第二届.NET技术交流会即将召开&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt;&lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/09/2nd-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助第二届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会：&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;报名&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;预告&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;幻灯片&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;演讲录象及下载&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/08/2nd-snda-dotnet-conference-sign-up.html#comments</comments>
      <pubDate>Mon, 16 Aug 2010 03:02:30 GMT</pubDate>
      <lastBuildDate>Mon, 16 Aug 2010 03:02:30 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载</title>
      <link>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html</link>
      <guid>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html</guid>
      <description>&lt;p&gt;经过几天的努力，终于将&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;首届.NET技术交流会&lt;/a&gt;的演讲录像制作完成了。本来在现在的高清视频以外，我还想像Channel 9一样提供一些低码率的格式下载，但多次尝试都以失败告终，各中滋味难以言喻。因此目前只能给大家提供mov格式的高清视频下载，对于Windows下各类强大的播放器都不成问题。您也可以在线观看这些视频，不过上传至优酷后，发现除了清晰度较低外，甚至还有音画不同步的问题。我正在联系&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;，会尽快用上质量更好的视频。&lt;/p&gt;

&lt;h1&gt;F#语言对异步程序设计的支持&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;如今的Web应用、 Silverlight以及各种分布式系统让异步解决方案有了更进一步的需求。F#是微软.NET平台上的函数式编程语言，并添加了不少让并行及异步编程变得有趣且轻松的特性。本次演讲将讨论F#的核心概念，并探讨F#中的不可变性、函数式设计、异步工作流、代理等特性是如何应对真实应用中的异步挑战的。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMjEzMDM1MDgw/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100624/1/fsharp-async-20100619-high.mov"&gt;高清视频下载&lt;/a&gt;（mov格式，1280 * 720，495M）&lt;/p&gt;

&lt;h1&gt;Rails: Better Framework, Better Life&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;吕国宁，Intridea.com，高级工程师&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;RoR是Ruby on Rails的缩写，是一个用于编写Web应用的框架。它基于Ruby语言，给开发人员提供了强大便利的框架支持。Ruby有很多优点，但是一直以来其流行范围仅局限于日本。2004年，当Rails框架横空出世，让人们认识到了一个更符合实际需要并且高效的web框架，在其出现不久就受到了业内的广泛关注。吕国宁将结合自己三年的Rails开发经验，给大家介绍一些Rails的优点，背后的设计文化，以及Rails的前景发展等内容。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMTg0MTI0NjU2/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100624/1/ror-20100619-high.mov"&gt;高清视频下载&lt;/a&gt;（mov格式，1280 * 720，432M）&lt;/p&gt;

&lt;h1&gt;大众点评网的技术变迁之路&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;王宏，大众点评网，架构师&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;大众点评网从2003年创建以来，已经经历了7个年头，在技术方面从最初构建时期的简单的、低成本的方案，到发展阶段不断“痛苦”的转型演变，到目前比较复杂的技术架构，大众点评网的技术团队一直在关注业界新技术，力求提高可用性、降低成本、优化用户体验，并针对“点评”这一第三方参与的特点，摸索出一些特有的解决方案，借此机会希望能够分享给大家。&lt;/p&gt;
&lt;embed src="http://player.youku.com/player.php/sid/XMTg0MTA5MTY0/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;&lt;a href="http://dlc2.sdo.com/FTP/cop/20100624/1/dianping-20100619-high.mov"&gt;高清视频下载&lt;/a&gt;（mov格式，1280 * 720，486M）&lt;/p&gt;

&lt;h1&gt;Q &amp;amp; A&lt;/h1&gt;

&lt;p&gt;按照计划，原本还会有一场关于C#的演讲，但该场的讲师由于突然有急事只得作罢。于是我在最后增加了“演讲嘉宾问答”的环节，您可以在&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;上进行在线观看。&lt;/p&gt;

&lt;p&gt;第1段：&lt;/p&gt;
&lt;embed src="http://player.ku6.com/refer/QFX3kb6ZJ8Wqge-w/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" allowfullscreen="true" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;p&gt;第2段：&lt;/p&gt;
&lt;embed src="http://player.ku6.com/refer/wmD_kenujbB6gxnM/v.swf" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" allowfullscreen="true" type="application/x-shockwave-flash"&gt;&lt;/embed&gt; 

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助首届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助首届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载 &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html#comments</comments>
      <pubDate>Thu, 24 Jun 2010 06:40:51 GMT</pubDate>
      <lastBuildDate>Thu, 24 Jun 2010 06:40:51 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片</title>
      <link>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html</link>
      <guid>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html</guid>
      <description>&lt;p&gt;今天是近期最热的一天，气温高达35度，异常闷热，但是依然有160多位朋友冒着酷暑参加了&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;首届.NET技术交流会&lt;/a&gt;，这让我感到很欣慰，因此这里首先要感谢大家的支持。我刚才浏览了一下三场演讲的桌面录像，可谓异常完美，现在只等酷六网的摄影师的讲师录像到手，便可以合成为最终的演讲视频了，希望能够尽快展示给大家。不过现在，大家可以在第一时间浏览本次活动新鲜出炉的幻灯片。&lt;/p&gt;

&lt;h1&gt;F#语言对异步程序设计的支持&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;如今的Web应用、 Silverlight以及各种分布式系统让异步解决方案有了更进一步的需求。F#是微软.NET平台上的函数式编程语言，并添加了不少让并行及异步编程变得有趣且轻松的特性。本次演讲将讨论F#的核心概念，并探讨F#中的不可变性、函数式设计、异步工作流、代理等特性是如何应对真实应用中的异步挑战的。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_4546454"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="F#语言对异步程序设计的支持" href="http://www.slideshare.net/jeffz/f-4546454"&gt;F#语言对异步程序设计的支持&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse4546454" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=f-100619111719-phpapp01&amp;stripped_title=f-4546454" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse4546454" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=f-100619111719-phpapp01&amp;stripped_title=f-4546454" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;h1&gt;Rails: Better Framework, Better Life&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;吕国宁，Intridea.com，高级工程师 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;RoR是Ruby on Rails的缩写，是一个用于编写Web应用的框架。它基于Ruby语言，给开发人员提供了强大便利的框架支持。Ruby有很多优点，但是一直以来其流行范围仅局限于日本。2004年，当Rails框架横空出世，让人们认识到了一个更符合实际需要并且高效的web框架，在其出现不久就受到了业内的广泛关注。吕国宁将结合自己三年的Rails开发经验，给大家介绍一些Rails的优点，背后的设计文化，以及Rails的前景发展等内容。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_4545483"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="Better Framework Better Life" href="http://www.slideshare.net/jeffz/better-framework-better-life"&gt;Better Framework Better Life&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse4545483" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=better-framework-better-life-100619101625-phpapp01&amp;stripped_title=better-framework-better-life" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse4545483" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=better-framework-better-life-100619101625-phpapp01&amp;stripped_title=better-framework-better-life" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;
&lt;/div&gt;

&lt;h1&gt;大众点评网的技术变迁之路&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;王宏，大众点评网，架构师 &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;大众点评网从2003年创建以来，已经经历了7个年头，在技术方面从最初构建时期的简单的、低成本的方案，到发展阶段不断“痛苦”的转型演变，到目前比较复杂的技术架构，大众点评网的技术团队一直在关注业界新技术，力求提高可用性、降低成本、优化用户体验，并针对“点评”这一第三方参与的特点，摸索出一些特有的解决方案，借此机会希望能够分享给大家。&lt;/p&gt;

&lt;div style="width: 425px" id="__ss_4545503"&gt;&lt;strong style="margin: 12px 0px 4px; display: block"&gt;&lt;a title="大众点评网的技术变迁之路" href="http://www.slideshare.net/jeffz/ss-4545503"&gt;大众点评网的技术变迁之路&lt;/a&gt;&lt;/strong&gt;&lt;object id="__sse4545503" width="425" height="355"&gt;&lt;param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100619101718-phpapp02&amp;stripped_title=ss-4545503" /&gt;&lt;param name="allowFullScreen" value="true"/&gt;&lt;param name="allowScriptAccess" value="always"/&gt;&lt;embed name="__sse4545503" src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=random-100619101718-phpapp02&amp;stripped_title=ss-4545503" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"&gt;&lt;/embed&gt;&lt;/object&gt;

  &lt;div style="padding-bottom: 12px; padding-left: 0px; padding-right: 0px; padding-top: 5px"&gt;View more &lt;a href="http://www.slideshare.net/"&gt;presentations&lt;/a&gt; from &lt;a href="http://www.slideshare.net/jeffz"&gt;jeffz&lt;/a&gt;.&lt;/div&gt;

&lt;/div&gt;&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助首届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助首届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片 &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html#comments</comments>
      <pubDate>Sat, 19 Jun 2010 15:48:23 GMT</pubDate>
      <lastBuildDate>Sat, 19 Jun 2010 15:48:23 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会即将召开</title>
      <link>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html</link>
      <guid>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html</guid>
      <description>&lt;p&gt;由&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;赞助的首届.NET技术大会将于6月19号下午1点召开，本次交流会请到了四位讲师，议题覆盖了F#、C#、Rails及架构等多个方面。我已经看过了各场演讲的幻灯片草稿，也很期待各位讲师在正式演讲中的表现。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-1-600x850.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/conf-1-600x850.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;本次大会中，我们还获得了人民邮电出版社&lt;a href="http://www.turingbook.com/Homepage/Default.aspx"&gt;图灵教育&lt;/a&gt;赠送的20册图书，将会作为奖品赠送给在交流会中表现积极的听众。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/turingbook.jpg" width="300" /&gt;&lt;/a&gt; 

&lt;p&gt;此外，我们还请到了&lt;a href="http://www.ku6.com/"&gt;酷六网&lt;/a&gt;的专业摄影师对演讲过程进行全程拍摄，并配合各位讲师自身的屏幕录像，将在后期合成为适合独立观看的演讲视频，让不能到场的朋友在线或是下载后观看。&lt;/p&gt;

&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/ku6-logo.jpg" /&gt; 

&lt;p&gt;本次会议的邀请函已经发给各位报名者，请携带邀请函至会议现场签到，&lt;span style="color:red;"&gt;没有报名的朋友可以在现场直接报名&lt;/span&gt;。&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;关于会议的时间、地点、交通、议程等更多信息，请关注会议的报名信息&lt;/a&gt;。&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html"&gt;盛大创新院赞助首届.NET技术交流会开始报名了！&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;盛大创新院赞助首届.NET技术交流会即将召开 &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html#comments</comments>
      <pubDate>Thu, 17 Jun 2010 03:45:06 GMT</pubDate>
      <lastBuildDate>Thu, 17 Jun 2010 03:45:06 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/cutting-edge/">技术尝鲜</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>盛大创新院赞助首届.NET技术交流会开始报名了！</title>
      <link>http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html</link>
      <guid>http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html</guid>
      <description>&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-in.png" /&gt; 

&lt;p&gt;自从上次在博客中提到&lt;a href="http://blog.zhaojie.me/2010/01/1651772.html"&gt;盛大创新院&lt;/a&gt;将会&lt;a href="http://blog.zhaojie.me/2010/03/snda-dotnet-conference-advices.html"&gt;赞助.NET技术会议&lt;/a&gt;已经过去了一个半月，如今这件事情终于落实了。我为此准备了数千字的申请书，但老大看也不看便表示支持。他的说法是，只要办得热烈，有影响力，那么这样的活动绝对支持。为此，各场次演讲内容及会场等诸多事宜之后，现在“首届.NET技术交流会”正式进入报名阶段了。&lt;strike&gt;人数不设上限，多多益善，怕只怕会场会显得空旷&lt;/strike&gt;，&lt;span style="color:red;"&gt;目前报名人数已达200人，请抓紧时间报名&lt;/span&gt;。除了.NET社区的群众以外，也欢迎其他技术社区的朋友前来参与交流。事实上，我组织技术交流会的目的之一便是希望能够促进.NET社区与其他技术社区的交流及相互学习。&lt;/p&gt;

&lt;h1&gt;时间及议程安排&lt;/h1&gt;

&lt;p&gt;本次交流会定于&lt;strong&gt;2010年6月19日&lt;/strong&gt;（周六）举行，具体时间如下。会议目前安排了四场演讲，除了与.NET技术直接相关的F#及C#话题以外，我们还邀请了大众点评网的核心架构师来分享他们在多年发展过程中的技术变迁过程。此外，我也联系了即将举办的Rails大会的主办方，邀请他们来分享关于Rails框架的内容。&lt;a href="http://blog.zhaojie.me/2010/05/learn-from-disadvantages.html"&gt;正如我之前说的那样&lt;/a&gt;，在以后每次技术会议上，我都会邀请其他社区的高手来讲解相关技术。&lt;/p&gt;

&lt;table style="text-align: center" border="1" cellspacing="0" cellpadding="5"&gt;&lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;时间&lt;/th&gt;

      &lt;th&gt;议程&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;&lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;12:30 ~ 13:00&lt;/td&gt;

      &lt;td&gt;签到&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;13:00 ~ 14:00&lt;/td&gt;

      &lt;td&gt;F#语言对异步程序设计的支持&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:00 ~ 14:10&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;14:10 ~ 15:10&lt;/td&gt;

      &lt;td&gt;Rails: Better Framework, Better Life&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:10 ~ 15:40&lt;/td&gt;

      &lt;td&gt;茶歇&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;15:40 ~ 16:40&lt;/td&gt;

      &lt;td&gt;C#语言开发模式及实用建议&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:40 ~ 16:50&lt;/td&gt;

      &lt;td&gt;短休&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;16:50 ~ 17: 50&lt;/td&gt;

      &lt;td&gt;大众点评网的技术变迁过程&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;&lt;/table&gt;

&lt;h1&gt;演讲内容&lt;/h1&gt;

&lt;p&gt;以下是关于四场演讲的详细描述。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/zhaojie.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;F#语言对异步程序设计的支持&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;赵劼，盛大创新院，研究员。关注前沿技术，并致力于开源社区与微软平台的组合优化。对函数式编程，并行程序开发，代码之美以及程序员能力与修养等相关问题也有着浓厚的兴趣，同时非常希望能够写程序到60岁。最近致力于F#，Scala语言及mono平台在社区中的推广。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;如今的Web应用、 Silverlight以及各种分布式系统让异步解决方案有了更进一步的需求。F#是微软.NET平台上的函数式编程语言，并添加了不少让并行及异步编程变得有趣且轻松的特性。本次演讲将讨论F#的核心概念，并探讨F#中的不可变性、函数式设计、异步工作流、代理等特性是如何应对真实应用中的异步挑战的。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/lvguoning.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/lvguoning.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;Rails: Better Framework, Better Life &lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;吕国宁，Intridea.com，高级工程师。超过三年的Ruby on Rails程序开发经验，先后供职于Red.com, ELCTech.com, 目前在Intridea从事ROR的咨询服务以及为客户提供解决方案。在工作之余热心国内ROR社区建设，长期致力于推动ROR社区在中国的发展，从2007年开始，作为组织者之一，在上海成立了Shanghaionrails组织，并筹办了国内第一，第二届RubyConfChina大会以及首届RailsConference。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;RoR是Ruby on Rails的缩写，是一个用于编写Web应用的框架。他基于Ruby语言，给开发人员提供了强大便利的框架支持。Ruby有很多优点，但是一直以来其流行范围仅局限于日本。2004年，当Rails框架横空出世，让人们认识到了一个更符合实际需要并且高效的web框架，在其出现不久就受到了业内的广泛关注。吕国宁将结合自己三年的Rails开发经验，给大家介绍一些Rails的优点，背后的设计文化，以及Rails的前景发展等内容。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/chenlifu.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/chenlifu.jpg" height="150" /&gt;&lt;/a&gt;

&lt;h2&gt;C#语言开发模式及实用建议&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;陈黎夫，盛大创新院，研究员&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;C#语言是微软.NET平台上的核心开发语言，从1.0到4.0的版本推进极其迅速，版本更替也带来了大量改进。在快速的发展过程中，C#的表达能力被一再强化，其功能愈发强大，语言本身也日趋复杂，这往往会让一些开发者在使用中产生迷茫，无法做到最优。希望能借此机会分享一下最新版本C#语言的开发模式以及实用建议。&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/wanghong.jpg" target="_blank"&gt;&lt;img class="floatRight" src="http://img.zhaojie.me/blog/snda-dotnet-conf/wanghong.jpg" height="150" /&gt;&lt;/a&gt; 

&lt;h2&gt;大众点评网的技术变迁之路&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;讲师：&lt;/strong&gt;王宏，大众点评网，架构师&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;大众点评网从2003年创建以来，已经经历了7个年头，在技术方面从最初构建时期的简单的、低成本的方案，到发展阶段不断“痛苦”的转型演变，到目前比较复杂的技术架构，大众点评网的技术团队一直在关注业界新技术，力求提高可用性、降低成本、优化用户体验，并针对“点评”这一第三方参与的特点，摸索出一些特有的解决方案，借此机会希望能够分享给大家。&lt;/p&gt;

&lt;h1&gt;地点&lt;/h1&gt;

&lt;p&gt;本次交流会举办地为&lt;strong&gt;上海市浦东新区碧波路888号畅星大厦&lt;/strong&gt;（地铁二号线张江高科站下，步行10分钟可达）3楼会议厅举行，地图如下：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-map.png" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;鸟瞰图：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-hybrid.jpg" width="400" /&gt;&lt;/a&gt;

&lt;p&gt;畅星大厦外观：&lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-building.jpg" width="400" /&gt;&lt;/a&gt;

&lt;p&gt;会场实景照片：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg"&gt;&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/changxing-room.jpg" width="400" /&gt;&lt;/a&gt; 

&lt;p&gt;会场容量可以容纳超过200人，希望到时候不会显得太过空旷。:)&lt;/p&gt;

&lt;h1&gt;报名信息&lt;/h1&gt;

&lt;img src="http://img.zhaojie.me/blog/snda-dotnet-conf/sign-up-now.jpg" /&gt;

&lt;p&gt;本次交流会&lt;strike&gt;&lt;a href="http://www.diaochapai.com/survey347021"&gt;现已开始报名，请填写报名表&lt;/a&gt;，报名截止日期为2010年6月13日&lt;/strike&gt;已经截止，请等待后续消息。感谢大家支持！&lt;/p&gt;

&lt;h1&gt;相关文章&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;盛大创新院赞助首届.NET技术交流会开始报名了！ &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-is-coming.html"&gt;盛大创新院赞助首届.NET技术交流会即将召开&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-all-slides.html"&gt;盛大创新院赞助首届.NET技术交流会 - 各场演讲幻灯片&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/06/first-snda-dotnet-conference-videos.html"&gt;盛大创新院赞助首届.NET技术交流会 - 演讲录像及下载&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2010/05/first-snda-dotnet-conference-sign-up.html#comments</comments>
      <pubDate>Thu, 13 May 2010 06:30:10 GMT</pubDate>
      <lastBuildDate>Thu, 13 May 2010 06:30:10 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>F#中的异步及并行模式（3 - 下）：代理的进一步使用</title>
      <link>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-more-agents.html</link>
      <guid>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-more-agents.html</guid>
      <description>&lt;p&gt;在本系列的第3部分中，我们会来探索F#中&lt;strong&gt;轻量级的，交互式的代理&lt;/strong&gt;，以及与代理有关的一些模式，包括&lt;strong&gt;隔离的内部状态&lt;/strong&gt;。（译注：由于原文内容较多，译文拆成两段进行。在&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-agents.html"&gt;上半段&lt;/a&gt;文章中讨论了代理的基本使用方式，而下半段则讨论关于代理使用中更进一步的模式。） &lt;/p&gt;  &lt;h1&gt;消息与联合类型&lt;/h1&gt;  &lt;p&gt;很多时候我们会使用联合类型（Union Type）作为消息的类型。例如，我将要展示一个基于代理的DirectX示例，我们要在模拟引擎中使用如下的消息： &lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;Message =
    | PleaseTakeOneStep
    | PleaseAddOneBall &lt;span style="color: blue"&gt;of &lt;/span&gt;Ball &lt;/pre&gt;

&lt;p&gt;模拟引擎中的代理：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;simulationEngine =
    Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
        &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do
                    &lt;/span&gt;&lt;span style="color: green"&gt;// Wait for a message
                    &lt;/span&gt;&lt;span style="color: blue"&gt;let! &lt;/span&gt;msg = inbox.Receive() 

                    &lt;span style="color: green"&gt;// Process a message
                    &lt;/span&gt;&lt;span style="color: blue"&gt;match &lt;/span&gt;msg &lt;span style="color: blue"&gt;with
                    &lt;/span&gt;| PleaseTakeOneStep &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;state.Transform moveBalls
                    | PleaseAddOneBall ball &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;state.AddObject ball  }) &lt;/pre&gt;

&lt;p&gt;在很多情况下使用强类型消息是个不错的做法。不过，在某些您需要和其他消息机制协作的时候，也无需担心使用如“obj”和“string”等泛化的消息类型，此时代理只需要在运行时进行类型判断或转化即可。 &lt;/p&gt;

&lt;h1&gt;参数化代理及抽象代理&lt;/h1&gt;

&lt;p&gt;代理只是F#编码中的一种设计模式。这意味着您可以将F#中各种常用的技巧，如参数化，抽象或是代码片段重用与代理一起使用。例如，您可以把之前的serveQuoteStream函数参数化，指定每条股票消息传输中的间隔时间：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Net.Sockets 

&lt;span style="color: green"&gt;/// serve up a stream of quotes
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;serveQuoteStream (client: TcpClient, &lt;span style="background-color: yellow"&gt;periodMilliseconds: int&lt;/span&gt;) = async {
    &lt;span style="color: blue"&gt;let &lt;/span&gt;stream = client.GetStream()
    &lt;span style="color: blue"&gt;while true do
        do! &lt;/span&gt;stream.AsyncWrite( &lt;span style="color: maroon"&gt;&amp;quot;AAPL 439.2&amp;quot;B &lt;/span&gt;)
        &lt;span style="color: blue"&gt;do! &lt;/span&gt;Async.Sleep &lt;span style="background-color: yellow"&gt;periodMilliseconds&lt;/span&gt;
} &lt;/pre&gt;

&lt;p&gt;这意味着您的股票服务器中不同的请求可以拥有不同长度的间隔。 &lt;/p&gt;

&lt;p&gt;与此类似，您可以使用函数参数，将整个代理类的功能进行抽象：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;iteratingAgent job =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do
               let! &lt;/span&gt;msg = inbox.Receive()
               &lt;span style="color: blue"&gt;do! &lt;/span&gt;job msg }) 

&lt;span style="color: blue"&gt;let &lt;/span&gt;foldingAgent job initialState =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     let rec &lt;/span&gt;loop state = async {
         &lt;span style="color: blue"&gt;let! &lt;/span&gt;msg = inbox.Receive()
         &lt;span style="color: blue"&gt;let! &lt;/span&gt;state = job state msg
         &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop state
       }
     loop initialState)&lt;/pre&gt;

&lt;p&gt;您可以这样使用第一个函数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent1 = iteratingAgent (&lt;span style="color: blue"&gt;fun &lt;/span&gt;msg &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;async { &lt;span style="color: blue"&gt;do &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;&amp;quot;got message '%s'&amp;quot;  &lt;/span&gt;msg }) &lt;/pre&gt;

&lt;p&gt;及第二个：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent2 =
    foldingAgent (&lt;span style="color: blue"&gt;fun &lt;/span&gt;state msg &lt;span style="color: blue"&gt;-&amp;gt;
        &lt;/span&gt;async { &lt;span style="color: blue"&gt;if &lt;/span&gt;state % 1000 = 0 &lt;span style="color: blue"&gt;then &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;&amp;quot;count = '%d'&amp;quot; &lt;/span&gt;msg;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;state + 1 }) 0 &lt;/pre&gt;

&lt;h1&gt;从代理返回结果&lt;/h1&gt;

&lt;p&gt;在以后的文章中，我们会讨论一些访问执行中的代理的部分结果的技巧，例如，我们可以使用每个MailboxProcessor代理的&lt;a href="http://msdn.microsoft.com/en-us/library/ee370407(VS.100).aspx"&gt;PostAndAsyncReply&lt;/a&gt;方法。这样的技巧在创建网络通信代理时显得尤其重要。 &lt;/p&gt;

&lt;p&gt;然而，这种做法很多时候有些过了，我们可能只是需要将结果汇报给一些如GUI般的监视环境。汇报部分结果的简单方法之一，便是之前在&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-2-reporting-progress-with-events.html"&gt;第二篇文章&lt;/a&gt;中讨论过的设计模式。下面便是这样一个例子，它创建了一个代理，对每1000条消息进行采样，并将得到的事件分发给GUI或其他管理线程（请注意，其中用到了第二篇文章中SynchronizationContext的两个扩展方法CaptureCurrent和RaiseEvent）。&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// Receive messages and raise an event on each 1000th message 
&lt;/span&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;SamplingAgent() = 
    &lt;span style="color: green"&gt;// The event that is raised 
    // Capture the synchronization context to allow us to raise events 
    // back on the GUI thread 
    &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;syncContext = SynchronizationContext.CaptureCurrent() 

    &lt;span style="color: green"&gt;// The internal mailbox processor agent 
    &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent = 
        &lt;span style="color: blue"&gt;new &lt;/span&gt;MailboxProcessor&amp;lt;_&amp;gt;(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
            &lt;/span&gt;async { &lt;span style="color: blue"&gt;let &lt;/span&gt;count = ref 0 
                    &lt;span style="color: blue"&gt;while true do 
                        let! &lt;/span&gt;msg = inbox.Receive() 
                        incr count 
                        &lt;span style="color: blue"&gt;if &lt;/span&gt;!count % 1000 = 0 &lt;span style="color: blue"&gt;then 
                            &lt;/span&gt;syncContext.RaiseEvent sample msg }) 

    &lt;span style="color: green"&gt;/// Post a message to the agent 
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Post msg = agent.Post msg 

    &lt;span style="color: green"&gt;/// Start the agent 
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Start () = agent.Start() 

    &lt;span style="color: green"&gt;/// Raised every 1000'th message 
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Sample = sample.Publish&lt;/pre&gt;

&lt;p&gt;您可以这样使用代理：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent = SamplingAgent() 

agent.Sample.Add (&lt;span style="color: blue"&gt;fun &lt;/span&gt;s &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;&amp;quot;sample: %s&amp;quot; &lt;/span&gt;s) 
agent.Start() 

&lt;span style="color: blue"&gt;for &lt;/span&gt;i = 0 &lt;span style="color: blue"&gt;to &lt;/span&gt;10000 &lt;span style="color: blue"&gt;do 
   &lt;/span&gt;agent.Post (sprintf &lt;span style="color: maroon"&gt;&amp;quot;message %d&amp;quot; &lt;/span&gt;i) &lt;/pre&gt;

&lt;p&gt;与预料一致，这会报告agent的消息采样： &lt;/p&gt;

&lt;pre class="code"&gt;sample: message 999 
sample: message 1999 
sample: message 2999 
sample: message 3999 
sample: message 4999 
sample: message 5999 
sample: message 6999 
sample: message 7999 
sample: message 8999 
sample: message 9999&lt;/pre&gt;

&lt;h1&gt;代理及错误&lt;/h1&gt;

&lt;p&gt;我们都无法避免错误和异常。良好的错误检测，报告及记录的措施是基于代理编程的基本要素。我们来看一下如何在F#的内存代理（MailboxProcessor）中检测和转发错误。 &lt;/p&gt;

&lt;p&gt;首先，F#异步代理的神奇之处在于异常可以由async { ... }自动捕获及分发，即使跨过多个异步等待及I/O操作。您也可以在async { ... }中使用try/with，try/finally及use关键字来捕获异常或释放资源。这意味着我们只需要在代理中处理那些未捕获的错误即可。 &lt;/p&gt;

&lt;p&gt;当MailboxProcessor代理中出现未捕获的异常时便会触发Error事件。一个常见的模式是将所有的错误转发给一个监视进程，例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;Agent&amp;lt;'T&amp;gt; = MailboxProcessor&amp;lt;'T&amp;gt; 

&lt;span style="color: blue"&gt;let &lt;/span&gt;supervisor = 
   Agent&amp;lt;System.Exception&amp;gt;.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do 
               let! &lt;/span&gt;err = inbox.Receive() 
               printfn &lt;span style="color: maroon"&gt;&amp;quot;an error occurred in an agent: %A&amp;quot; &lt;/span&gt;err }) 

&lt;span style="color: blue"&gt;let &lt;/span&gt;agent = 
   &lt;span style="color: blue"&gt;new &lt;/span&gt;Agent&amp;lt;int&amp;gt;(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do 
               let! &lt;/span&gt;msg = inbox.Receive() 
               &lt;span style="color: blue"&gt;if &lt;/span&gt;msg % 1000 = 0 &lt;span style="color: blue"&gt;then 
                   &lt;/span&gt;failwith &lt;span style="color: maroon"&gt;&amp;quot;I don't like that cookie!&amp;quot; &lt;/span&gt;}) 

&lt;span style="background-color: yellow"&gt;agent.Error.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;error &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;supervisor.Post error)&lt;/span&gt; 
agent.Start() &lt;/pre&gt;

&lt;p&gt;我们也可以很方便地并行这些配置操作：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent = 
   &lt;span style="color: blue"&gt;new &lt;/span&gt;Agent&amp;lt;int&amp;gt;(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do 
               let! &lt;/span&gt;msg = inbox.Receive() 
               &lt;span style="color: blue"&gt;if &lt;/span&gt;msg % 1000 = 0 &lt;span style="color: blue"&gt;then 
                   &lt;/span&gt;failwith &lt;span style="color: maroon"&gt;&amp;quot;I don't like that cookie!&amp;quot; &lt;/span&gt;}) 
   |&amp;gt; Agent.reportErrorsTo supervisor 
   |&amp;gt; Agent.start &lt;/pre&gt;

&lt;p&gt;或使用辅助模块：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;module &lt;/span&gt;Agent = 
   &lt;span style="color: blue"&gt;let &lt;/span&gt;reportErrorsTo (supervisor: Agent&amp;lt;exn&amp;gt;) (agent: Agent&amp;lt;_&amp;gt;) = 
       &lt;span style="background-color: yellow"&gt;agent.Error.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;error &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;supervisor.Post error); agent&lt;/span&gt;

   &lt;span style="color: blue"&gt;let &lt;/span&gt;start (agent: Agent&amp;lt;_&amp;gt;) = agent.Start(); agent &lt;/pre&gt;

&lt;p&gt;下面是一个例子，我们创建了10000个代理，其中某些会报告错误：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;supervisor = 
   Agent&amp;lt;int * System.Exception&amp;gt;.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do 
               let! &lt;/span&gt;(agentId, err) = inbox.Receive() 
               printfn &lt;span style="color: maroon"&gt;&amp;quot;an error '%s' occurred in agent %d&amp;quot; &lt;/span&gt;err.Message agentId }) 

&lt;span style="color: blue"&gt;let &lt;/span&gt;agents = 
   [ &lt;span style="color: blue"&gt;for &lt;/span&gt;agentId &lt;span style="color: blue"&gt;in &lt;/span&gt;0 .. 10000 &lt;span style="color: blue"&gt;-&amp;gt; 
        let &lt;/span&gt;agent = 
            &lt;span style="color: blue"&gt;new &lt;/span&gt;Agent&amp;lt;string&amp;gt;(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
               &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do 
                         let! &lt;/span&gt;msg = inbox.Receive() 
                         &lt;span style="color: blue"&gt;if &lt;/span&gt;msg.Contains(&lt;span style="color: maroon"&gt;&amp;quot;agent 99&amp;quot;&lt;/span&gt;) &lt;span style="color: blue"&gt;then 
                             &lt;/span&gt;failwith &lt;span style="color: maroon"&gt;&amp;quot;I don't like that cookie!&amp;quot; &lt;/span&gt;}) 
        agent.Error.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;error &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;supervisor.Post (agentId,error)) 
        agent.Start() 
        (agentId, agent) ]&lt;/pre&gt;

&lt;p&gt;我们发送消息：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(agentId, agent) &lt;span style="color: blue"&gt;in &lt;/span&gt;agents &lt;span style="color: blue"&gt;do 
   &lt;/span&gt;agent.Post (sprintf &lt;span style="color: maroon"&gt;&amp;quot;message to agent %d&amp;quot; &lt;/span&gt;agentId ) &lt;/pre&gt;

&lt;p&gt;便可看到： &lt;/p&gt;

&lt;pre class="code"&gt;an error 'I don't like that cookie!' occurred in agent 99 
an error 'I don't like that cookie!' occurred in agent 991 
an error 'I don't like that cookie!' occurred in agent 992 
an error 'I don't like that cookie!' occurred in agent 993 
... 
an error 'I don't like that cookie!' occurred in agent 9999 &lt;/pre&gt;

&lt;p&gt;这一节我们处理了F#内存中的MailboxProcessor代理发生的错误。其他一些代理（例如，表示服务器端请求的代理）也可以这样进行设计与架构，以便进行优雅的错误转发及重试。 &lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;隔离的代理是一种常用的编程模式，它不断运用在各种编程领域中，从设备驱动编程到用户界面，还包括分布式编程及高度伸缩的通信服务器。每次您编写了一个对象，线程或是异步工作程序，用于处理一个长时间的通信（如向声卡发送数据，从网络读取数据，或是响应一个输入的事件流），您其实就是在编写一种代理。每次您在写一个ASP.NET网页处理程序时，其实您也在使用一种形式的代理（每次调用时都重置状态）。在各种情况下，隔离与通信有关的状态是很常见的需求。 &lt;/p&gt;

&lt;p&gt;隔离的代理是一种最终的实现方式──例如，实现可伸缩的编程算法，包括可伸缩的请求服务器及分布式编程算法。与其他各种异步及并发编程模式一样，它们也不能被滥用。然而，他们是一种优雅、强大且高效的技术，使用非常广泛。 &lt;/p&gt;

&lt;p&gt;F#是一个独特的，随Visual Studio 2010一同出现的托管语言，完整支持轻量级的异步计算及内存种的代理。在F#中，异步代理可以通过组合的形式编写，而不用使用回调函数或控制反转等方式（译注：个人认为，事实上F#的这种做法其实是种优雅的控制反转）。这里有些权衡的地方──例如：在以后的文章中，我们会观察如何使用.NET类库中标准的APM模式来释放您的代理。然而，优势也是很明显的：易于控制，伸缩性强，并且在需要的时候，便可以在组织起CPU和I/O并行操作的同时，保持CPU密集型代码在.NET中的完整性能。&lt;/p&gt;

&lt;p&gt;当然，也有其他一些.NET或基于JVM的语言支持轻量级的交互式代理──早前，有人认为这在.NET是“不可能”的事情，因为线程的代价十分昂贵。而如今，F#在2007年引入了“async { ... }”，这被视为语言设计上的一个突破──它让程序员可以在一个被业界广泛认可的编程平台上构建轻量级、组合式的异步编程及交互式的代理。除了Axum语言原型（它也受了F#的影响）之外，F#还证明了一个异步语言特性是一个完全可行的方法，这也解放了如今业界运行时系统设计领域的一个争论话题：我们是否要将线程做得轻量？ &lt;/p&gt;

&lt;p&gt;F#异步编程可以认为是一种“抢占”的实现，在此之前也有先驱的各种投入。例如&lt;a href="http://okmij.org/ftp/Computation/Continuations.html"&gt;OCaml delimited continuations&lt;/a&gt;，&lt;a href="http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.39.8039"&gt;Haskell embedding of monadic concurrency&lt;/a&gt;及各种强调了并行中&lt;a href="http://www.cs.missouri.edu/~harrisonwl/drafts/CheapThreads.pdf"&gt;抢占&lt;/a&gt;的重要性的论文。 &lt;/p&gt;

&lt;p&gt;您可以在.NET 2.0、3.5及4.0、以及&lt;a href="http://www.mono-project.com/Main_Page"&gt;Linux/Mono/Mac&lt;/a&gt;、还有&lt;a href="http://silverlight.net/"&gt;Silverlight&lt;/a&gt;中使用异步代理。此外，您甚至可以使用&lt;a href="http://www.intellifactory.com/"&gt;WebSharper&lt;/a&gt;平台将F#的异步编程模型翻译成JavaScript执行。好好享受吧！&lt;/p&gt;

&lt;p&gt;原文：&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx"&gt;Async and Parallel Design Patterns in F#: Agents&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-more-agents.html#comments</comments>
      <pubDate>Sun, 21 Mar 2010 10:19:00 GMT</pubDate>
      <lastBuildDate>Sun, 21 Mar 2010 10:19:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>F#中的异步及并行模式（3 - 上）：代理的基本使用</title>
      <link>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-agents.html</link>
      <guid>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-agents.html</guid>
      <description>&lt;p&gt;在本系列的第3部分中，我们会来探索F#中&lt;strong&gt;轻量级的，交互式的代理&lt;/strong&gt;，以及与代理有关的一些模式，包括“隔离的内部状态”。（译注：由于原文较长，因此译文分为两段，目前是第一段，讲解了F#中异步代理的基本使用方式。）&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html"&gt;第1部分&lt;/a&gt;描述了F#作为一个并行及异步语言，是如何支持轻量级的响应操作，并给出了CPU异步并行和I/O异步并行两种模式。 &lt;/li&gt;  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-2-reporting-progress-with-events.html"&gt;第2部分&lt;/a&gt;描述了如何从异步计算或后台计算单元中获得结果。 &lt;/li&gt;&lt;/ul&gt;  &lt;h1&gt;模式4：您的第一个代理&lt;/h1&gt;  &lt;p&gt;我们来观察您所创建的第一个异步代理：&lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;Agent&amp;lt;'T&amp;gt; = MailboxProcessor&amp;lt;'T&amp;gt;
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;agent =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do
               let! &lt;/span&gt;msg = inbox.Receive()
               printfn &lt;span style="color: maroon"&gt;&amp;quot;got message '%s'&amp;quot; &lt;/span&gt;msg } )&lt;/pre&gt;

&lt;p&gt;这个代理不断地异步等待消息，并将它们打印出来。在这段代码中，每个消息都是一个字符串，且agent的类型是：&lt;/p&gt;

&lt;pre class="code"&gt;agent.Post &lt;span style="color: maroon"&gt;&amp;quot;hello!&amp;quot;&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;这便会打印出：&lt;/p&gt;

&lt;pre class="code"&gt;got message 'hello!'&lt;/pre&gt;

&lt;p&gt;也可以这样发送多条消息：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;i &lt;span style="color: blue"&gt;in &lt;/span&gt;1 .. 10000 &lt;span style="color: blue"&gt;do
   &lt;/span&gt;agent.Post (sprintf &lt;span style="color: maroon"&gt;&amp;quot;message %d&amp;quot; &lt;/span&gt;i)&lt;/pre&gt;

&lt;p&gt;这样便可以打印出10000条消息。 &lt;/p&gt;

&lt;p&gt;您可以认为每个代理对象都包含一个消息队列（或管道），并在消息到达时进行响应。一个委托一般都使用异步的循环等待来消息并进行处理。如在上面的例子中，代理使用while循环进行处理。 &lt;/p&gt;

&lt;p&gt;许多读者可能已经对代理颇为熟悉了。如&lt;a href="http://ftp.sunet.se/pub/lang/erlang/"&gt;Erlang&lt;/a&gt;，它便是基于代理设计的（在那里被称为进程）。而不久之前，一个基于.NET平台的实验性的孵化型语言，&lt;a href="http://msdn.microsoft.com/en-us/devlabs/dd795202.aspx"&gt;Axum&lt;/a&gt;，也注重了基于代理编程的重要性。Axum与F#中的代理设计相互影响，而其他包含轻量级线程的语言也强调了基于代理的组合与设计。 &lt;/p&gt;

&lt;p&gt;上面的例子一开始创建了一个类型的缩写：Agent，它代表了F#类库中基于内存的代理类型“&lt;a href="http://msdn.microsoft.com/en-us/library/ee370357(VS.100).aspx"&gt;MailboxProcessor&lt;/a&gt;”。如果您愿意的话也可以使用这个完整的名字，不过我更喜欢简单的命名。 &lt;/p&gt;

&lt;h1&gt;您的第一批10万个代理&lt;/h1&gt;

&lt;p&gt;代理对象非常轻量，这是因为它基于F#的异步编程模型。例如，您可以在一个.NET进程中创建成百上千，甚至更多个代理。例如，我们来创建10万个简单的代理对象：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agents =
    [ &lt;span style="color: blue"&gt;for &lt;/span&gt;i &lt;span style="color: blue"&gt;in &lt;/span&gt;0 .. 100000 &lt;span style="color: blue"&gt;-&amp;gt;
       &lt;/span&gt;Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
         &lt;/span&gt;async { &lt;span style="color: blue"&gt;while true do
                   let! &lt;/span&gt;msg = inbox.Receive()
                   &lt;span style="color: blue"&gt;if &lt;/span&gt;i % 10000 = 0 &lt;span style="color: blue"&gt;then
                       &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;&amp;quot;agent %d got message '%s'&amp;quot; &lt;/span&gt;i msg } ) ]&lt;/pre&gt;

&lt;p&gt;您可以这样向每个代理对象发送消息：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;agent &lt;span style="color: blue"&gt;in &lt;/span&gt;agents &lt;span style="color: blue"&gt;do
    &lt;/span&gt;agent.Post &lt;span style="color: maroon"&gt;&amp;quot;ping!&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;每第1万个代理对象会在收到消息时打印信息。这个代理集合在处理消息时非常迅速，只要几秒钟时间。代理和内存中的消息处理非常快。 &lt;/p&gt;

&lt;p&gt;很显然，代理并不与.NET线程直接对应──您不可能在单个应用程序中创建10万的线程（在32位操作系统中，即便1000个线程也已经太多了）。相反，在代理等待消息时，它实际上只是表现为一个回调函数，一些对象分配，以及代理所引用的闭包等等。在收到消息之后，代理的工作会在一个线程池（默认便是.NET线程池）中分配并执行。 &lt;/p&gt;

&lt;p&gt;尽管需要10万个代理的情况并不多见，不过2000多个代理倒是很正常的。接下来我们便会看到这样一些例子。 &lt;/p&gt;

&lt;h1&gt;高伸缩的Web服务器处理请求&lt;/h1&gt;

&lt;p&gt;在F#编程中，异步代理的思想其实是一种在多个环境中反复出现的设计模式。在F#中，我们经常使用“代理”这个词表示一种随时发生的，特别是通过循环，或是处理消息，或是产生结果的异步计算。 &lt;/p&gt;

&lt;p&gt;例如，在以后的文章中，我们会来关注如何使用F#构建伸缩性强的TCP或HTTP服务器应用程序，并将它们部署到&lt;a href="http://aws.amazon.com/ec2/"&gt;EC2&lt;/a&gt;或是&lt;a href="http://www.microsoft.com/windowsazure/"&gt;Windows Azure&lt;/a&gt;中去。这里我们打算用“股票服务器”作为例子，它接受TCP或HTTP连接，并向客户端返回一系列的股票信息。每个客户端会每隔一秒钟收到一条股票信息。这个服务最终会以单个URL或REST API的形式发布。 &lt;/p&gt;

&lt;p&gt;在实现时，我们为每个客户端请求分配一个异步代理（由于只是演示，我们在这里便不断地写入相同的AAPL股票信息）：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Net.Sockets
 
&lt;span style="color: green"&gt;/// serve up a stream of quotes
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;serveQuoteStream (client: TcpClient) = async {
    &lt;span style="color: blue"&gt;let &lt;/span&gt;stream = client.GetStream()
    &lt;span style="color: blue"&gt;while true do
        do! &lt;/span&gt;stream.AsyncWrite( &lt;span style="color: maroon"&gt;&amp;quot;AAPL 200.38&amp;quot;B &lt;/span&gt;)
        &lt;span style="color: blue"&gt;do! &lt;/span&gt;Async.Sleep 1000.0 &lt;span style="color: green"&gt;// sleep one second&lt;/span&gt;}&lt;/pre&gt;

&lt;p&gt;每个代理会一直运行到客户端连接断开。因为代理非常轻量，因此这个股票服务能够在一台机器上支持数千个并发连接（如果使用云托管服务则会有更好的伸缩性）。而同一时刻会出现多少个代理对象则取决于客户端的数量。 &lt;/p&gt;

&lt;p&gt;上面的例子演示了使用F#进行网络编程是多么的方便──网络协议在此变成了基于异步代理的数据流读写。在以后的文章中我们会观察更多使用F#进行伸缩性强的TCP/HTTP编程。 &lt;/p&gt;

&lt;h1&gt;代理与隔离状态（命令式）&lt;/h1&gt;

&lt;p&gt;F#代理编程的一个优秀的关键之处便是其&lt;strong&gt;隔离性&lt;/strong&gt;。隔离性则意味着资源“归属”与某个特定的代理，而不会暴露给其他代理。因此，独立状态对并发的访问及数据竞争是一种良好的保护。 &lt;/p&gt;

&lt;p&gt;在F#中，异步代理的独立性直接表现为文法上的作用域。例如，下面的代码使用一个字典来累计发送至代理对象的不同消息的次数。内部的字典在文法上是异步代理私有的，因此我们无法在代理外部对字典进行读写。这意味着字典的可变状态实际上是被隔离的，代理保证了对它的非并发的安全访问。&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;Agent&amp;lt;'T&amp;gt; = MailboxProcessor&amp;lt;'T&amp;gt;
 
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Collections.Generic
&lt;span style="color: blue"&gt;let &lt;/span&gt;agent =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;let &lt;/span&gt;strings = Dictionary&amp;lt;string,int&amp;gt;()
             &lt;span style="color: blue"&gt;while true do
               let! &lt;/span&gt;msg = inbox.Receive()
               &lt;span style="color: blue"&gt;if &lt;/span&gt;strings.ContainsKey msg &lt;span style="color: blue"&gt;then
                   &lt;/span&gt;strings.[msg] &amp;lt;- strings.[msg] + 1
               &lt;span style="color: blue"&gt;else
                   &lt;/span&gt;strings.[msg] &amp;lt;- 0
               printfn &lt;span style="color: maroon"&gt;&amp;quot;message '%s' now seen '%d' times&amp;quot; &lt;/span&gt;msg strings.[msg] } )&lt;/pre&gt;

&lt;p&gt;状态隔离是F#的基本特性，它并不是代理所独有的。例如，下面的代码对StreamReader和ResizeArray（这是F#中对System.Collections.Generics.List的别称）的隔离访问。请注意这段代码和.NET类库中的System.IO.File.ReadAllLines方法功能相同：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;readAllLines (file:string) =
    &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;System.IO.StreamReader(file)
    &lt;span style="color: blue"&gt;let &lt;/span&gt;lines = ResizeArray&amp;lt;_&amp;gt;()
    &lt;span style="color: blue"&gt;while &lt;/span&gt;not reader.EndOfStream &lt;span style="color: blue"&gt;do
        &lt;/span&gt;lines.Add (reader.ReadLine())
    lines.ToArray()&lt;/pre&gt;

&lt;p&gt;在这里，reader和ResizeArray对象都无法在函数外部使用。在代理或其他持续计算的情况里，隔离性是至关重要的──状态在这里永远独立于程序运行中的其他部分。 &lt;/p&gt;

&lt;p&gt;说到底，隔离性是个动态的属性，它经常受到文法的约束。例如，考虑这样一个延迟的，随需加载的读取器，它会读取文件中的所有行：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;readAllLines (file:string) =
    seq { &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;System.IO.StreamReader(file)
          &lt;span style="color: blue"&gt;while &lt;/span&gt;not reader.EndOfStream &lt;span style="color: blue"&gt;do
              yield &lt;/span&gt;reader.ReadLine() }&lt;/pre&gt;

&lt;p&gt;隔离状态经常包含可变的值，包括F#中的引用单元。例如，下面的代码在一个引用单元中不断累计接受到的消息个数：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     &lt;/span&gt;async { &lt;span style="color: blue"&gt;let &lt;/span&gt;count = ref 0
             &lt;span style="color: blue"&gt;while true do
               let! &lt;/span&gt;msg = inbox.Receive()
               incr count
               printfn &lt;span style="color: maroon"&gt;&amp;quot;now seen a total of '%d' messages&amp;quot; &lt;/span&gt;!count } )&lt;/pre&gt;

&lt;p&gt;再次强调，这里可变的状态被隔离了，确保了对它单线程的安全访问。 &lt;/p&gt;

&lt;h1&gt;在代理中使用循环和隔离状态（函数式）&lt;/h1&gt;

&lt;p&gt;F#程序员了解两种实现循环的方法：使用“命令式”的while/for以及可变的累加器（&lt;strong&gt;ref&lt;/strong&gt;或&lt;strong&gt;mutable&lt;/strong&gt;），或是“函数式”风格的调用，将累加状态作为参数传递给一个或多个递归函数。例如，计算文件行数的程序可以使用命令式的方式来写：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;countLines (file:string) =
    &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;System.IO.StreamReader(file)
    &lt;span style="color: blue"&gt;let &lt;/span&gt;count = ref 0
    &lt;span style="color: blue"&gt;while &lt;/span&gt;not reader.EndOfStream &lt;span style="color: blue"&gt;do
        &lt;/span&gt;reader.ReadLine() |&amp;gt; ignore
        incr count
    !count&lt;/pre&gt;

&lt;p&gt;或是递归式的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;countLines (file:string) =
    &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;System.IO.StreamReader(file)
    &lt;span style="color: blue"&gt;let rec &lt;/span&gt;loop n =
        &lt;span style="color: blue"&gt;if &lt;/span&gt;reader.EndOfStream &lt;span style="color: blue"&gt;then &lt;/span&gt;n
        &lt;span style="color: blue"&gt;else
            &lt;/span&gt;reader.ReadLine() |&amp;gt; ignore
            loop (n+1)
    loop 0&lt;/pre&gt;

&lt;p&gt;在使用时，两种方式都是可行的：函数式的做法相对更加高级一些，但是大大减少了代码中显式的状态修改次数，且更为通用。两种写法对于F#程序员来说，一般都可以理解，他们还可以将“while”循环转化为等价的“let rec”函数（这是个不错的面试问题！）。 &lt;/p&gt;

&lt;p&gt;有趣的是，在编写异步循环时的规则也一样：您可以使用命令式的“while”或函数式的“let rec”中的任意一种来定义循环。例如，这里有一个利用递归的异步函数统计消息数量的做法：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     let rec &lt;/span&gt;loop n = async {
         &lt;span style="color: blue"&gt;let! &lt;/span&gt;msg = inbox.Receive()
         printfn &lt;span style="color: maroon"&gt;&amp;quot;now seen a total of %d messages&amp;quot; &lt;/span&gt;(n+1)
         &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop (n+1)
     }
     loop 0 )&lt;/pre&gt;

&lt;p&gt;这样我们便可以获得这样的输出：&lt;/p&gt;

&lt;pre class="code"&gt;now seen a total of 0 messages
now seen a total of 1 messages
....
now seen a total of 10000 messages&lt;/pre&gt;

&lt;p&gt;再提一次，定义代理对象的两种常见模式为命令式的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent =
   Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
     &lt;/span&gt;async {
         &lt;span style="color: green"&gt;// isolated imperative state goes here
         &lt;/span&gt;...
         &lt;span style="color: blue"&gt;while &lt;/span&gt;&amp;lt;condition&amp;gt; &lt;span style="color: blue"&gt;do
             &lt;/span&gt;&lt;span style="color: green"&gt;// read messages and respond
             &lt;/span&gt;...
     })&lt;/pre&gt;

&lt;p&gt;及函数式的：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;agent = 
    Agent.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt;
      let rec &lt;/span&gt;loop arg1 arg2 = async { 
          &lt;span style="color: green"&gt;// receive and process messages here
          &lt;/span&gt;...
          &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop newArg1 newArg2
       }

      loop initialArg1 initialArg2)&lt;/pre&gt;

&lt;p&gt;再次强调，两种方法在F#都是合理的──使用递归的异步函数可能是更高级的方法，但更为函数式且更为通用。&lt;/p&gt;

&lt;p&gt;原文：&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/02/15/async-and-parallel-design-patterns-in-f-part-3-agents.aspx"&gt;Async and Parallel Design Patterns in F#: Agents&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-agents.html#comments</comments>
      <pubDate>Sun, 14 Mar 2010 17:35:00 GMT</pubDate>
      <lastBuildDate>Sun, 14 Mar 2010 17:35:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>F#中的异步及并行模式（2）：反馈进度的事件（包含Twitter示例）</title>
      <link>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-2-reporting-progress-with-events.html</link>
      <guid>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-2-reporting-progress-with-events.html</guid>
      <description>&lt;p&gt;在这篇文章中，我们将关注一个常见的异步模式：&lt;strong&gt;反馈进度的事件（Reporting Progress with Events）&lt;/strong&gt;。在文章最后，我们会使用这个设计模式开发一个示例，从Twitter中获取一系列记录。 &lt;/p&gt; &lt;p&gt;这是&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html"&gt;F#异步编程基础系列&lt;/a&gt;的第二部分。其中部分示例代码来自于&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/01/04/beta2-updates-to-the-f-jaoo-tutorial-code.aspx"&gt;F# JAOO Tutorial&lt;/a&gt;。 &lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html"&gt;第1部分&lt;/a&gt;描述了F#作为一个并行及异步语言，是如何支持轻量级的响应操作，并给出了&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html"&gt;CPU异步并行&lt;/a&gt;和&lt;a href="http://blogs.msdn.com/tiny_mce/jscripts/tiny_mce/Mandler-paper.pdf"&gt;I/O异步并行&lt;/a&gt;两种模式。  &lt;li&gt;第2部分便是本文。  &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-agents.html"&gt;第3部分（上&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-more-agents.html"&gt;下&lt;/a&gt;）则描述了F#中轻量级，响应式的独立代理对象。 &lt;/li&gt;&lt;/ul&gt; &lt;h1&gt;模式3：反馈进度的事件&lt;/h1&gt; &lt;p&gt;我们先来看一下这个设计模式的一个基础示例。在下面的代码中，我们会定义一个对象，以此来协调一组同时执行的异步任务。每个任务在结束之后会主动汇报它的结果，而不是等待统一的收集过程。 &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;AsyncWorker&amp;lt;'T&amp;gt;(jobs: seq&amp;lt;Async&amp;lt;'T&amp;gt;&amp;gt;) =
 
    &lt;span style="color: green"&gt;// This declares an F# event that we can raise&lt;/span&gt;
    &lt;span style="color: blue"&gt;let &lt;/span&gt;jobCompleted  = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;int * 'T&amp;gt;()
 
    &lt;span style="color: green"&gt;/// Start an instance of the work
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Start()    =
        &lt;span style="color: green"&gt;// Capture the synchronization context to allow us to raise events back on the GUI thread&lt;/span&gt;
        &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;syncContext = SynchronizationContext.CaptureCurrent()&lt;/span&gt;
 
        &lt;span style="color: green"&gt;// Mark up the jobs with numbers
        &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;jobs = jobs |&amp;gt; Seq.mapi (&lt;span style="color: blue"&gt;fun &lt;/span&gt;i job &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;(job,i+1))
 
        &lt;span style="color: blue"&gt;let &lt;/span&gt;work = 
            Async.Parallel
               [ &lt;span style="color: blue"&gt;for &lt;/span&gt;(job,jobNumber) &lt;span style="color: blue"&gt;in &lt;/span&gt;jobs &lt;span style="color: blue"&gt;-&amp;gt;
                   &lt;/span&gt;async { &lt;span style="color: blue"&gt;let! &lt;/span&gt;result = job
                           syncContext.RaiseEvent jobCompleted (jobNumber,result)
                           &lt;span style="color: blue"&gt;return &lt;/span&gt;result } ]
 
        Async.Start(work |&amp;gt; Async.Ignore)
 
    &lt;span style="color: green"&gt;/// Raised when a particular job completes
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.JobCompleted  = jobCompleted.Publish&lt;/pre&gt;
&lt;p&gt;设计模式的一些关键之处已经使用黄色进行高亮：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在对象的Start方法中，我们在GUI线程中捕获了当前的“同步上下文”，这使得我们可以从GUI的上下文中运行代码或触发事件。我们还定义了一个私有的辅助函数来触发任意的F#事件，这虽不必须但可以使我们的代码变的更为整洁。 
&lt;li&gt;定义了多个事件。这些事件作为属性发布，如果该对象还需要被其他.NET语言使用，则为它标记一个[&amp;lt;CLIEvent&amp;gt;]属性。 
&lt;li&gt;我们这里通过指定一个定义了任务内容的异步工作流来启动后台任务。Async.Start可以用来启动这个工作流（虽然Async.StartWithContinuations更为常用，例如在后面的示例中）。在后台任务产生进度之后，便会在合适的时候触发这些事件。 &lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;这段代码使用了两个基于System.Threading.SynchronizationContext的辅助方法，它们会在这个系列的文章中多次出现。如下： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;SynchronizationContext &lt;span style="color: blue"&gt;with
    &lt;/span&gt;&lt;span style="color: green"&gt;/// A standard helper extension method to raise an event on the GUI thread
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;syncContext.RaiseEvent (event: Event&amp;lt;_&amp;gt;) args =
        &lt;/span&gt;syncContext.Post((&lt;span style="color: blue"&gt;fun &lt;/span&gt;_ &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;event.Trigger args),state=&lt;span style="color: blue"&gt;null&lt;/span&gt;)
 
    &lt;span style="color: green"&gt;/// A standard helper extension method to capture the current synchronization context.
    /// If none is present, use a context that executes work in the thread pool.
    &lt;/span&gt;&lt;span style="color: blue"&gt;static member &lt;/span&gt;CaptureCurrent () =
        &lt;span style="color: blue"&gt;match &lt;/span&gt;SynchronizationContext.Current &lt;span style="color: blue"&gt;with
        &lt;/span&gt;| &lt;span style="color: blue"&gt;null -&amp;gt; new &lt;/span&gt;SynchronizationContext()
        | ctxt &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;ctxt&lt;/pre&gt;
&lt;p&gt;您现在便可以使用这个组件来管理一系列CPU密集型异步任务： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let rec &lt;/span&gt;fib i = &lt;span style="color: blue"&gt;if &lt;/span&gt;i &amp;lt; 2 &lt;span style="color: blue"&gt;then &lt;/span&gt;1 &lt;span style="color: blue"&gt;else &lt;/span&gt;fib (i-1) + fib (i-2)
   
    &lt;span style="color: blue"&gt;let &lt;/span&gt;worker =
        &lt;span style="color: blue"&gt;new &lt;/span&gt;AsyncWorker&amp;lt;_&amp;gt;( [ &lt;span style="color: blue"&gt;for &lt;/span&gt;i &lt;span style="color: blue"&gt;in &lt;/span&gt;1 .. 100 &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;async { &lt;span style="color: blue"&gt;return &lt;/span&gt;fib (i % 40) } ] )
 
    worker.JobCompleted.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;(jobNumber, result) &lt;span style="color: blue"&gt;-&amp;gt;
        &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"job %d completed with result %A" &lt;/span&gt;jobNumber result)
 
    worker.Start()&lt;/pre&gt;
&lt;p&gt;在执行时，每个任务结束之后便会汇报结果：&lt;/p&gt;&lt;pre class="code"&gt;job 1 completed with result 1
job 2 completed with result 2
...
job 39 completed with result 102334155
job 77 completed with result 39088169
job 79 completed with result 102334155&lt;/pre&gt;
&lt;p&gt;我们可以使用多种方式让后台运行的任务汇报结果。在90%的情况下最简单的便是上面的方法：在GUI（或ASP.NET的Page_Load）线程中触发.NET事件。这个技巧隐藏了后台线程的使用细节，并利用了所有.NET程序员都非常熟悉的标准.NET惯例，以此保证用于实现并行编程的技术都得到了有效的封装。 &lt;/p&gt;
&lt;h1&gt;汇报异步I/O的进度&lt;/h1&gt;
&lt;p&gt;反馈进度的事件模式也可以用在异步I/O操作上。例如这里有一系列I/O任务： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System.IO
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Net
&lt;span style="color: blue"&gt;open &lt;/span&gt;Microsoft.FSharp.Control.WebExtensions

&lt;span style="color: green"&gt;/// Fetch the contents of a web page, asynchronously.
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;httpAsync(url:string) =
    async { &lt;span style="color: blue"&gt;let &lt;/span&gt;req = WebRequest.Create(url)
            &lt;span style="color: blue"&gt;use! &lt;/span&gt;resp = req.AsyncGetResponse()
            &lt;span style="color: blue"&gt;use &lt;/span&gt;stream = resp.GetResponseStream()
            &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(stream)
            &lt;span style="color: blue"&gt;let &lt;/span&gt;text = reader.ReadToEnd()
            &lt;span style="color: blue"&gt;return &lt;/span&gt;text }

&lt;span style="color: blue"&gt;let &lt;/span&gt;urls =
    [ &lt;span style="color: maroon"&gt;"http://www.live.com"&lt;/span&gt;;
      &lt;span style="color: maroon"&gt;"http://news.live.com"&lt;/span&gt;;
      &lt;span style="color: maroon"&gt;"http://www.yahoo.com"&lt;/span&gt;;
      &lt;span style="color: maroon"&gt;"http://news.yahoo.com"&lt;/span&gt;;
      &lt;span style="color: maroon"&gt;"http://www.google.com"&lt;/span&gt;;
      &lt;span style="color: maroon"&gt;"http://news.google.com"&lt;/span&gt;; ]

&lt;span style="color: blue"&gt;let &lt;/span&gt;jobs =  [ &lt;span style="color: blue"&gt;for &lt;/span&gt;url &lt;span style="color: blue"&gt;in &lt;/span&gt;urls &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;httpAsync url ]

&lt;span style="color: blue"&gt;let &lt;/span&gt;worker = &lt;span style="color: blue"&gt;new &lt;/span&gt;AsyncWorker&amp;lt;_&amp;gt;(jobs)
worker.JobCompleted.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;(jobNumber, result) &lt;span style="color: blue"&gt;-&amp;gt;
    &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"job %d completed with result %A" &lt;/span&gt;jobNumber result.Length)

worker.Start()&lt;/pre&gt;
&lt;p&gt;在执行过程中便会反馈进度结果，表现为每个Web页面的长度： &lt;/p&gt;&lt;pre class="code"&gt;job 5 completed with result 8521
job 6 completed with result 155767
job 3 completed with result 117778
job 1 completed with result 16490
job 4 completed with result 175186
job 2 completed with result 70362&lt;/pre&gt;
&lt;h1&gt;反馈多种不同事件的任务&lt;/h1&gt;
&lt;p&gt;在这个设计模式中，我们使用了一个对象来封装和监督异步组合任务的执行过程，即使我们需要丰富API，也可以轻松地添加多个事件。例如，以下的代码添加了额外的事件来表示所有的任务已经完成了，或是其中某个任务出现了错误，还有便是整个组合完成之前便成功地取消了任务。以下高亮的代码便展示了事件的声明，触发及发布： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Threading
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.IO
&lt;span style="color: blue"&gt;open &lt;/span&gt;Microsoft.FSharp.Control.WebExtensions
 
&lt;span style="color: blue"&gt;type &lt;/span&gt;AsyncWorker&amp;lt;'T&amp;gt;(jobs: seq&amp;lt;Async&amp;lt;'T&amp;gt;&amp;gt;) =
 
         
    &lt;span style="color: green"&gt;// Each of these lines declares an F# event that we can raise&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;allCompleted  = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;'T[]&amp;gt;()&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;error         = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;System.Exception&amp;gt;()&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;canceled      = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;System.OperationCanceledException&amp;gt;()&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;jobCompleted  = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;int * 'T&amp;gt;()&lt;/span&gt;
 
    &lt;span style="color: blue"&gt;let &lt;/span&gt;cancellationCapability = &lt;span style="color: blue"&gt;new &lt;/span&gt;CancellationTokenSource()
 
    &lt;span style="color: green"&gt;/// Start an instance of the work
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Start()    =                                                       
                                                      
        &lt;span style="color: green"&gt;// Capture the synchronization context to allow us to raise events back on the GUI thread
        &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;syncContext = SynchronizationContext.CaptureCurrent()
 
        &lt;span style="color: green"&gt;// Mark up the jobs with numbers
        &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;jobs = jobs |&amp;gt; Seq.mapi (&lt;span style="color: blue"&gt;fun &lt;/span&gt;i job &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;(job,i+1))
 
        &lt;span style="color: blue"&gt;let &lt;/span&gt;work = 
            Async.Parallel
               [ &lt;span style="color: blue"&gt;for &lt;/span&gt;(job,jobNumber) &lt;span style="color: blue"&gt;in &lt;/span&gt;jobs &lt;span style="color: blue"&gt;-&amp;gt;
                   &lt;/span&gt;async { &lt;span style="color: blue"&gt;let! &lt;/span&gt;result = job
                           &lt;span style="background-color: yellow"&gt;syncContext.RaiseEvent jobCompleted (jobNumber,result)&lt;/span&gt;
                           &lt;span style="color: blue"&gt;return &lt;/span&gt;result } ]
 
        Async.StartWithContinuations
            ( work,
              (&lt;span style="color: blue"&gt;fun &lt;/span&gt;res &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;&lt;span style="background-color: yellow"&gt;raiseEventOnGuiThread allCompleted res)&lt;/span&gt;,
              (&lt;span style="color: blue"&gt;fun &lt;/span&gt;exn &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;&lt;span style="background-color: yellow"&gt;raiseEventOnGuiThread error exn)&lt;/span&gt;,
              (&lt;span style="color: blue"&gt;fun &lt;/span&gt;exn &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;&lt;span style="background-color: yellow"&gt;raiseEventOnGuiThread canceled exn )&lt;/span&gt;,
             cancellationCapability.Token)
 
    &lt;span style="color: blue"&gt;member &lt;/span&gt;x.CancelAsync() =
       cancellationCapability.Cancel()
       
    &lt;span style="color: green"&gt;/// Raised when a particular job completes&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.JobCompleted  = jobCompleted.Publish&lt;/span&gt;
    &lt;span style="color: green"&gt;/// Raised when all jobs complete&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.AllCompleted  = allCompleted.Publish&lt;/span&gt;
    &lt;span style="color: green"&gt;/// Raised when the composition is cancelled successfully&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Canceled   = canceled.Publish&lt;/span&gt;
    &lt;span style="color: green"&gt;/// Raised when the composition exhibits an error&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Error      = error.Publish&lt;/pre&gt;&lt;/span&gt;
&lt;p&gt;我们可以使用最普通的做法来响应这些额外的事件，例如： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;worker = &lt;span style="color: blue"&gt;new &lt;/span&gt;AsyncWorker&amp;lt;_&amp;gt;(jobs)

worker.JobCompleted.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;(jobNumber, result) &lt;span style="color: blue"&gt;-&amp;gt;
    &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"job %d completed with result %A" &lt;/span&gt;jobNumber result.Length)

worker.AllCompleted.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;results &lt;span style="color: blue"&gt;-&amp;gt;
    &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"all done, results = %A" &lt;/span&gt;results )

worker.Start()&lt;/pre&gt;
&lt;p&gt;如上，这个监视中异步工作流可以支持任务的取消操作。 &lt;/p&gt;
&lt;h1&gt;推啊推啊推啊推&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;反馈进度的事件&lt;/strong&gt;模式可用于相当部分需要全程汇报进度的场景。在下一个示例中，我们使用这个模式来封装后台对于一系列&lt;a href="http://twitter.com"&gt;Twitter&lt;/a&gt;采样消息的读取操作（请参考&lt;a href="http://apiwiki.twitter.com/"&gt;Twitter API&lt;/a&gt;页面）。运行这个示例需要一个Twitter帐号和密码。在这里只会发起一个事件，如果需要的话您也可以在某些情况下发起更多事件。 &lt;/p&gt;
&lt;p&gt;&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/01/04/beta2-updates-to-the-f-jaoo-tutorial-code.aspx"&gt;F# JAOO Tutorial&lt;/a&gt;中也包含了这个示例。 &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: green"&gt;// F# Twitter Feed Sample using F# Async Programming and Event processing
//
 
&lt;/span&gt;#r &lt;span style="color: maroon"&gt;"System.Web.dll"
&lt;/span&gt;#r &lt;span style="color: maroon"&gt;"System.Windows.Forms.dll"
&lt;/span&gt;#r &lt;span style="color: maroon"&gt;"System.Xml.dll"
 
&lt;/span&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Globalization
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.IO
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Net
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Web
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Threading
&lt;span style="color: blue"&gt;open &lt;/span&gt;Microsoft.FSharp.Control.WebExtensions
 
&lt;span style="color: green"&gt;/// A component which listens to tweets in the background and raises an
/// event each time a tweet is observed
&lt;/span&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;TwitterStreamSample(userName:string, password:string) =
 
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;tweetEvent = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;_&amp;gt;()&lt;/span&gt;
    &lt;span style="color: blue"&gt;let &lt;/span&gt;streamSampleUrl = &lt;span style="color: maroon"&gt;"http://stream.twitter.com/1/statuses/sample.xml?delimited=length"
 
    &lt;/span&gt;&lt;span style="color: green"&gt;/// The cancellation condition
    &lt;/span&gt;&lt;span style="color: blue"&gt;let mutable &lt;/span&gt;group = &lt;span style="color: blue"&gt;new &lt;/span&gt;CancellationTokenSource()
 
    &lt;span style="color: green"&gt;/// Start listening to a stream of tweets
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;this.StartListening() =
                                                       
        &lt;span style="color: green"&gt;// Capture the synchronization context to allow us to raise events back on the GUI thread
        
        // Capture the synchronization context to allow us to raise events back on the GUI thread
        &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;syncContext = SynchronizationContext.CaptureCurrent()
 
        &lt;span style="color: green"&gt;/// The background process
        &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;listener (syncContext: SynchronizationContext) =
            async { &lt;span style="color: blue"&gt;let &lt;/span&gt;credentials = NetworkCredential(userName, password)
                    &lt;span style="color: blue"&gt;let &lt;/span&gt;req = WebRequest.Create(streamSampleUrl, Credentials=credentials)
                    &lt;span style="color: blue"&gt;use! &lt;/span&gt;resp = req.AsyncGetResponse()
                    &lt;span style="color: blue"&gt;use &lt;/span&gt;stream = resp.GetResponseStream()
                    &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(stream)
                    &lt;span style="color: blue"&gt;let &lt;/span&gt;atEnd = reader.EndOfStream
                    &lt;span style="color: blue"&gt;let rec &lt;/span&gt;loop() =
                        async {
                            &lt;span style="color: blue"&gt;let &lt;/span&gt;atEnd = reader.EndOfStream
                            &lt;span style="color: blue"&gt;if &lt;/span&gt;not atEnd &lt;span style="color: blue"&gt;then
                                let &lt;/span&gt;sizeLine = reader.ReadLine()
                                &lt;span style="color: blue"&gt;let &lt;/span&gt;size = int sizeLine
                                &lt;span style="color: blue"&gt;let &lt;/span&gt;buffer = Array.zeroCreate size
                                &lt;span style="color: blue"&gt;let &lt;/span&gt;_numRead = reader.ReadBlock(buffer,0,size) 
                                &lt;span style="color: blue"&gt;let &lt;/span&gt;text = &lt;span style="color: blue"&gt;new &lt;/span&gt;System.String(buffer)
                                &lt;span style="background-color: yellow"&gt;syncContext.RaiseEvent tweetEvent text&lt;/span&gt;
                                &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop()
                        }
                    &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop() }
 
        Async.Start(listener, group.Token)
 
    &lt;span style="color: green"&gt;/// Stop listening to a stream of tweets
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;this.StopListening() =
        group.Cancel();
        group &amp;lt;- &lt;span style="color: blue"&gt;new &lt;/span&gt;CancellationTokenSource()
 
    &lt;span style="color: green"&gt;/// Raised when the XML for a tweet arrives&lt;/span&gt;
    &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;this.NewTweet = tweetEvent.Publish&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;在Twitter的标准采样消息流中每出现一条消息便会触发一个事件，并同时提供消息的内容。我们可以这样监听事件流： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;userName = &lt;span style="color: maroon"&gt;"..." &lt;/span&gt;&lt;span style="color: green"&gt;// set Twitter user name here
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;password = &lt;span style="color: maroon"&gt;"..." &lt;/span&gt;&lt;span style="color: green"&gt;// set Twitter user name here
 
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;twitterStream = &lt;span style="color: blue"&gt;new &lt;/span&gt;TwitterStreamSample(userName, password)
 
twitterStream.NewTweet
   |&amp;gt; Event.add (&lt;span style="color: blue"&gt;fun &lt;/span&gt;s &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"%A" &lt;/span&gt;s)
 
twitterStream.StartListening()
twitterStream.StopListening()&lt;/pre&gt;
&lt;p&gt;程序运行后便会不断打印出每条消息的XML数据（很快啊！）。您可以从&lt;a href="http://apiwiki.twitter.com/"&gt;Twitter API&lt;/a&gt;页面中来了解采样消息流的使用方式。 &lt;/p&gt;
&lt;p&gt;如果您想同时解析这些消息，以下便是这一工作的示例代码。不过，也请关注&lt;a href="http://apiwiki.twitter.com/"&gt;Twitter API&lt;/a&gt;页面中的指导准则。例如，如果需要构建一个高可靠性的系统，您最好在处理前进行保存，或是使用消息队列。 &lt;/p&gt;&lt;pre class="code"&gt;#r &lt;span style="color: maroon"&gt;"System.Xml.dll"
&lt;/span&gt;#r &lt;span style="color: maroon"&gt;"System.Xml.Linq.dll"
&lt;/span&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Xml
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Xml.Linq
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;xn (s:string) = XName.op_Implicit s
 
&lt;span style="color: green"&gt;/// The results of the parsed tweet
&lt;/span&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;UserStatus =
    { UserName : string
      ProfileImage : string
      Status : string
      StatusDate : DateTime }
 
&lt;span style="color: green"&gt;/// Attempt to parse a tweet
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;parseTweet (xml: string) =
 
    &lt;span style="color: blue"&gt;let &lt;/span&gt;document = XDocument.Parse xml
   
    &lt;span style="color: blue"&gt;let &lt;/span&gt;node = document.Root
    &lt;span style="color: blue"&gt;if &lt;/span&gt;node.Element(xn &lt;span style="color: maroon"&gt;"user"&lt;/span&gt;) &amp;lt;&amp;gt; &lt;span style="color: blue"&gt;null then
        &lt;/span&gt;Some { UserName     = node.Element(xn &lt;span style="color: maroon"&gt;"user"&lt;/span&gt;).Element(xn &lt;span style="color: maroon"&gt;"screen_name"&lt;/span&gt;).Value;
               ProfileImage = node.Element(xn &lt;span style="color: maroon"&gt;"user"&lt;/span&gt;).Element(xn &lt;span style="color: maroon"&gt;"profile_image_url"&lt;/span&gt;).Value;
               Status       = node.Element(xn &lt;span style="color: maroon"&gt;"text"&lt;/span&gt;).Value       |&amp;gt; HttpUtility.HtmlDecode;
               StatusDate   = node.Element(xn &lt;span style="color: maroon"&gt;"created_at"&lt;/span&gt;).Value |&amp;gt; (&lt;span style="color: blue"&gt;fun &lt;/span&gt;msg &lt;span style="color: blue"&gt;-&amp;gt;
                                   &lt;/span&gt;DateTime.ParseExact(msg, &lt;span style="color: maroon"&gt;"ddd MMM dd HH:mm:ss +0000 yyyy"&lt;/span&gt;,
                                                       CultureInfo.CurrentCulture)); }
    &lt;span style="color: blue"&gt;else
        &lt;/span&gt;None&lt;/pre&gt;
&lt;p&gt;基于事件流还可以使用组合式的编程： &lt;/p&gt;&lt;pre class="code"&gt;twitterStream.NewTweet
   |&amp;gt; Event.choose parseTweet
   |&amp;gt; Event.add (&lt;span style="color: blue"&gt;fun &lt;/span&gt;s &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"%A" &lt;/span&gt;s)
 
twitterStream.StartListening()&lt;/pre&gt;
&lt;p&gt;或是收集统计数据： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;addToMultiMap key x multiMap =
   &lt;span style="color: blue"&gt;let &lt;/span&gt;prev = &lt;span style="color: blue"&gt;match &lt;/span&gt;Map.tryFind key multiMap &lt;span style="color: blue"&gt;with &lt;/span&gt;None &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;[] | Some v &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;v
   Map.add x.UserName (x::prev) multiMap
 
&lt;span style="color: green"&gt;/// An event which triggers on every 'n' triggers of the input event
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;every n (ev:IEvent&amp;lt;_&amp;gt;) =
   &lt;span style="color: blue"&gt;let &lt;/span&gt;out = &lt;span style="color: blue"&gt;new &lt;/span&gt;Event&amp;lt;_&amp;gt;()
   &lt;span style="color: blue"&gt;let &lt;/span&gt;count = ref 0
   ev.Add (&lt;span style="color: blue"&gt;fun &lt;/span&gt;arg &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;incr count; &lt;span style="color: blue"&gt;if &lt;/span&gt;!count % n = 0 &lt;span style="color: blue"&gt;then &lt;/span&gt;out.Trigger arg)
   out.Publish
 
twitterStream.NewTweet
   |&amp;gt; Event.choose parseTweet
   &lt;span style="color: green"&gt;// Build up the table of tweets indexed by user
   &lt;/span&gt;|&amp;gt; Event.scan (&lt;span style="color: blue"&gt;fun &lt;/span&gt;z x &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;addToMultiMap x.UserName x z) Map.empty
   &lt;span style="color: green"&gt;// Take every 20’ˉth index
   &lt;/span&gt;|&amp;gt; every 20
   &lt;span style="color: green"&gt;// Listen and display the average of #tweets/user
   &lt;/span&gt;|&amp;gt; Event.add (&lt;span style="color: blue"&gt;fun &lt;/span&gt;s &lt;span style="color: blue"&gt;-&amp;gt;
        let &lt;/span&gt;avg = s |&amp;gt; Seq.averageBy (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(KeyValue(_,d)) &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;float d.Length)
        printfn &lt;span style="color: maroon"&gt;"#users = %d, avg tweets = %g" &lt;/span&gt;s.Count avg)
 
twitterStream.StartListening()&lt;/pre&gt;
&lt;p&gt;以上代码对采样消息流的内容进行统计，每收到20条消息便打印出每个用户的平均推数。 &lt;/p&gt;&lt;pre class="code"&gt;#users = 19, avg tweets = 1.05263
#users = 39, avg tweets = 1.02564
#users = 59, avg tweets = 1.01695
#users = 79, avg tweets = 1.01266
#users = 99, avg tweets = 1.0101
#users = 118, avg tweets = 1.01695
#users = 138, avg tweets = 1.01449
#users = 158, avg tweets = 1.01266
#users = 178, avg tweets = 1.01124
#users = 198, avg tweets = 1.0101
#users = 218, avg tweets = 1.00917
#users = 237, avg tweets = 1.01266
#users = 257, avg tweets = 1.01167
#users = 277, avg tweets = 1.01083
#users = 297, avg tweets = 1.0101
#users = 317, avg tweets = 1.00946
#users = 337, avg tweets = 1.0089
#users = 357, avg tweets = 1.0084
#users = 377, avg tweets = 1.00796
#users = 396, avg tweets = 1.0101
#users = 416, avg tweets = 1.00962
#users = 435, avg tweets = 1.01149
#users = 455, avg tweets = 1.01099
#users = 474, avg tweets = 1.01266
#users = 494, avg tweets = 1.01215
#users = 514, avg tweets = 1.01167
#users = 534, avg tweets = 1.01124
#users = 554, avg tweets = 1.01083
#users = 574, avg tweets = 1.01045
#users = 594, avg tweets = 1.0101&lt;/pre&gt;
&lt;p&gt;只要使用稍稍不同的分析方式，我们便可以显示出Twitter提供的采样消息流中发推超过1次的用户，以及他们最新的推内容。以下代码可以在F#的交互命令行中使用，如&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/01/08/f-interactive-tips-and-tricks-visualizing-data-in-a-grid.aspx"&gt;之前文章中的做法，在数据表格中显示内容&lt;/a&gt;： &lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Drawing
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Windows.Forms
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;form = &lt;span style="color: blue"&gt;new &lt;/span&gt;Form(Visible = &lt;span style="color: blue"&gt;true&lt;/span&gt;, Text = &lt;span style="color: maroon"&gt;"A Simple F# Form"&lt;/span&gt;, TopMost = &lt;span style="color: blue"&gt;true&lt;/span&gt;, Size = Size(600,600))
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;data = &lt;span style="color: blue"&gt;new &lt;/span&gt;DataGridView(Dock = DockStyle.Fill, Text = &lt;span style="color: maroon"&gt;"F# Programming is Fun!"&lt;/span&gt;,
                            Font = &lt;span style="color: blue"&gt;new &lt;/span&gt;Font(&lt;span style="color: maroon"&gt;"Lucida Console"&lt;/span&gt;,12.0f),
                            ForeColor = Color.DarkBlue)
 
form.Controls.Add(data)
 
data.DataSource &amp;lt;- [| (10,10,10) |]
 
data.Columns.[0].Width &amp;lt;- 200
data.Columns.[2].Width &amp;lt;- 500
 
twitterStream.NewTweet
   |&amp;gt; Event.choose parseTweet
   &lt;span style="color: green"&gt;// Build up the table of tweets indexed by user
   &lt;/span&gt;|&amp;gt; Event.scan (&lt;span style="color: blue"&gt;fun &lt;/span&gt;z x &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;addToMultiMap x.UserName x z) Map.empty
   &lt;span style="color: green"&gt;// Take every 20’ˉth index
   &lt;/span&gt;|&amp;gt; every 20
   &lt;span style="color: green"&gt;// Listen and display those with more than one tweet
   &lt;/span&gt;|&amp;gt; Event.add (&lt;span style="color: blue"&gt;fun &lt;/span&gt;s &lt;span style="color: blue"&gt;-&amp;gt;
        let &lt;/span&gt;moreThanOneMessage = s |&amp;gt; Seq.filter (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(KeyValue(_,d)) &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;d.Length &amp;gt; 1) 
        data.DataSource &amp;lt;- 
            moreThanOneMessage
            |&amp;gt; Seq.map (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(KeyValue(user,d)) &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;(user, d.Length, d.Head.Status))
            |&amp;gt; Seq.filter (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(_,n,_) &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;n &amp;gt; 1)
            |&amp;gt; Seq.sortBy (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(_,n,_) &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;-n)
            |&amp;gt; Seq.toArray)
 
twitterStream.StartListening()&lt;/pre&gt;
&lt;p&gt;以下是部分采样结果： &lt;/p&gt;&lt;img src="http://img.zhaojie.me/blog/fsharp-tweets.jpg"&gt; 
&lt;p&gt;请注意，在上面的示例中，我们使用阻塞式的I/O操作来读取Twitter消息流。这有两个原因──Twitter数据流十分活跃(且一直如此)，而且我们可以假设不会有太多的Twitter流──如这里只有1个。此外，Twitter会对单一帐号的采样次数进行限制。文章后续的内容中，我们会演示如何对此类XML片段进行非阻塞的读取。 &lt;/p&gt;
&lt;h1&gt;用F#做并行，用C#/VB做GUI&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;反馈进度的事件&lt;/strong&gt;模式，对于那种F#程序员实现异步计算组件，并交给C#或VB程序员来使用的场景非常有用。在下面的示例中，发布出去的事件需要被标记为[&amp;lt;CLIEvent&amp;gt;]，以此保证它们在C#或VB程序员看来也是标准的事件。例如在上面第二个示例中，您需要使用：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: green"&gt;/// Raised when a particular job completes&lt;/span&gt;
&lt;span style="background-color: yellow"&gt;[&amp;lt;CLIEvent&amp;gt;]&lt;/span&gt;
&lt;span style="color: blue"&gt;member &lt;/span&gt;x.JobCompleted  = jobCompleted.Publish

&lt;span style="color: green"&gt;/// Raised when all jobs complete&lt;/span&gt;
&lt;span style="background-color: yellow"&gt;[&amp;lt;CLIEvent&amp;gt;]&lt;/span&gt;
&lt;span style="color: blue"&gt;member &lt;/span&gt;x.AllCompleted  = allCompleted.Publish

&lt;span style="color: green"&gt;/// Raised when the composition is cancelled successfully&lt;/span&gt;
&lt;span style="background-color: yellow"&gt;[&amp;lt;CLIEvent&amp;gt;]&lt;/span&gt;
&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Canceled   = canceled.Publish

&lt;span style="color: green"&gt;/// Raised when the composition exhibits an error&lt;/span&gt;
&lt;span style="background-color: yellow"&gt;[&amp;lt;CLIEvent&amp;gt;]&lt;/span&gt;
&lt;span style="color: blue"&gt;member &lt;/span&gt;x.Error      = error.Publish&lt;/pre&gt;
&lt;h1&gt;模式的限制&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;反馈进度的事件&lt;/strong&gt;模式会有一些假设：并行处理组件的使用者是那些GUI应用程序（如Windows Forms），服务器端应用程序（如ASP.NET）或其他一些能够将事件交由监控方使用场景。我们也可以调整这一模式中发起事件的方式，例如将消息发送给一个MailboxProcessor或简单地记录它们。然而这里还是有一些假设，需要有个主线程或是其他某个监控者来监听这些事件，或是合理的保存它们。 &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反馈进度的事件&lt;/strong&gt;模式同样假设封装后对象可以获取GUI线程的同步上下文，这通常是隐式的（如上面那些例子）。这一般是个合理的假设。还有一种做法是由外部参数来获得这个上下文，虽然它在.NET编程中并非是种常见的做法。 &lt;/p&gt;
&lt;p&gt;如果您对于.NET 4.0中的&lt;a href="http://msdn.microsoft.com/en-us/library/dd990377(VS.100).aspx"&gt;IObservable&lt;/a&gt;接口较为熟悉，您可能会考虑让TwitterStreamSample类型实现这个接口。然而，对于最终数据源来说，这个做法的好处不大。例如，以后TwitterStreamSample类型可能会需要提供更多种事件，例如在发生错误并自动重建连接时汇报，或是汇报暂停或延迟状况。在这样的场景中，发起.NET事件就够了，部分原因是为了让更多.NET程序员熟悉这个对象。在F#种，所有发布出去的IEvent&amp;lt;_&amp;gt;对象会自动实现IObservable，这样其他人在使用时便可以直接使用Observable组合器。 &lt;/p&gt;
&lt;h1&gt;结论&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;反馈进度的事件&lt;/strong&gt;模式是一种用于强大而优雅的做法，用于在某个边界之后对并行的执行过程加以封装，并同时汇报执行的结果或是进度。 &lt;/p&gt;
&lt;p&gt;在外部，AsyncWoker对象的表现形式一般是单线程的。假设您的异步输入是独立的，这意味着该组件不需要将程序的其他部分暴露在多线程的竞争条件下面。所有的JavaScript，ASP.NET以及GUI框架的程序员（如Windows Forms）都明白，框架的单线程特性既是优势也是劣势──问题变得简单了（没有数据竞争），但并行和异步编程却变得很困难。在.NET编程中，I/O和繁重的CPU计算必须交由后台线程去处理。上面的设计模式可以同时给您两个世界的优势：您得到了独立的，可以互操作的，通信丰富的后台处理组件，其中包括了对I/O及并行计算的支持，同时还在您的大部分代码中保留了单线程GUI编程的简单性。正如之前表现的那样，这些组件还保持了很高的通用性及可复用性，这使得独立的单元测试也变得非常容易。 &lt;/p&gt;
&lt;p&gt;在以后的文章里，我们会关注F#中其他一些并行及响应式编程方面的设计方式，包括： &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义轻量级的异步代理对象 &lt;/li&gt;
&lt;li&gt;使用async构建.NET任务 &lt;/li&gt;
&lt;li&gt;使用async调用.NET的APM模式 &lt;/li&gt;
&lt;li&gt;取消异步操作&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;原文：&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/01/10/async-and-parallel-design-patterns-in-f-reporting-progress-with-events-plus-twitter-sample.aspx"&gt;Async and Parallel Design Patterns in F#: Reporting Progress with Events (Plus Twitter Sample)&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-2-reporting-progress-with-events.html#comments</comments>
      <pubDate>Sun, 07 Mar 2010 16:33:00 GMT</pubDate>
      <lastBuildDate>Sun, 07 Mar 2010 16:33:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>F#中的异步及并行模式（1）：并行CPU及I/O计算</title>
      <link>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html</link>
      <guid>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html</guid>
      <description>&lt;p&gt;最后还是忍不住翻译文章了。这系列的文章谈论的是F#中常见的异步及并行模式，作者为F#语言的主要设计者&lt;a href="http://research.microsoft.com/en-us/people/dsyme/"&gt;Don Syme&lt;/a&gt;。异步相关的编程是F#语言中最重要的优势之一（我甚至在考虑“之一”两个字能否去掉）。F#是一门非常有特色的语言，是一门能够开阔眼界，改变您编程思路的语言，它经过了几年设计以及多个预览之后终于要正式露面了——此刻不上，更待何时。&lt;/p&gt;  &lt;h1&gt;介绍&lt;/h1&gt;  &lt;p&gt;F#是一门&lt;strong&gt;并行（parallel）&lt;/strong&gt;及&lt;strong&gt;响应式（reactive）&lt;/strong&gt;语言。这个说法意味着一个F#程序可以存在多个进行中的运算（如使用.NET线程进行F#计算），或是多个等待中的回应（如等待事件或消息的回调函数及代理对象）。 &lt;/p&gt;  &lt;p&gt;F#的异步表达式是简化异步及响应式程序编写的方式之一。在这篇及今后的文章中，我会探讨一些使用F#进行异步编程的基本方式──大致说来，它们都是F#异步编程时使用的模式。这里我假设您已经掌握了async的基本使用方式，如&lt;a href="http://msdn.microsoft.com/en-us/library/dd233250(VS.100).aspx"&gt;入门指南&lt;/a&gt;中的内容。 &lt;/p&gt;  &lt;p&gt;我们从两个简单的设计模式开始：&lt;strong&gt;CPU异步并行（Parallel CPU Asyncs）&lt;/strong&gt;和&lt;strong&gt;I/O异步并行（Paralle I/O Asyncs）&lt;/strong&gt;。 &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;本系列的&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-2-reporting-progress-with-events.html"&gt;第2部分&lt;/a&gt;描述了如何从异步计算或后台计算单元中获得结果。 &lt;/li&gt;    &lt;li&gt;&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-agents.html"&gt;第3部分（上&lt;/a&gt;、&lt;a href="http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-3-more-agents.html"&gt;下&lt;/a&gt;）则描述了F#中轻量级的，响应式的，各自独立的代理对象。 &lt;/li&gt; &lt;/ul&gt;  &lt;h1&gt;模式1：CPU异步并行&lt;/h1&gt;  &lt;p&gt;首先来了解第一个模式：&lt;strong&gt;CPU异步并行&lt;/strong&gt;，这意味着并行地开展一系列的CPU密集型计算。下面的代码计算的是斐波那契数列，它会将这些计算进行并行地调配： &lt;/p&gt;  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let rec &lt;/span&gt;fib x = &lt;span style="color: blue"&gt;if &lt;/span&gt;x &amp;lt;= 2 &lt;span style="color: blue"&gt;then &lt;/span&gt;1 &lt;span style="color: blue"&gt;else &lt;/span&gt;fib(x-1) + fib(x-2)
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;fibs =
    Async.Parallel [ &lt;span style="color: blue"&gt;for &lt;/span&gt;i &lt;span style="color: blue"&gt;in &lt;/span&gt;0..40 &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;async { &lt;span style="color: blue"&gt;return &lt;/span&gt;fib(i) } ]
    |&amp;gt; Async.RunSynchronously&lt;/pre&gt;

&lt;p&gt;结果是： &lt;/p&gt;

&lt;pre class="code"&gt;val fibs : int array =
     [|1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377; 610; 987; 1597; 2584;
       4181; 6765; 10946; 17711; 28657; 46368; 75025; 121393; 196418; 317811;
       514229; 832040; 1346269; 2178309; 3524578; 5702887; 9227465; 14930352;
       24157817; 39088169; 63245986; 102334155|]&lt;/pre&gt;

&lt;p&gt;上面的代码展示了并行CPU异步计算模式的要素：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;“async { … }”用于指定一系列的CPU任务。 &lt;/li&gt;

  &lt;li&gt;这些任务使用&lt;a href="http://msdn.microsoft.com/en-us/library/ee353779(VS.100).aspx"&gt;Async.Parallel&lt;/a&gt;进行fork-join式的组合。 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在这里，我们使用&lt;a href="http://msdn.microsoft.com/en-us/library/ee370262(VS.100).aspx"&gt;Async.RunSynchronously&lt;/a&gt;方法来执行组合后的任务，这会启动一个异步任务，并同步地等待其最后结果。您可以使用这个模式来完成各种CPU并行（例如对矩阵乘法进行划分和并行计算）或是批量处理任务。 &lt;/p&gt;

&lt;h1&gt;模式2：I/O异步并行&lt;/h1&gt;

&lt;p&gt;现在我们已经展示了在F#中进行CPU密集型并行编程的方式。F#异步编程的重点之一，便是可以用相同的方式进行CPU和I/O密集型的计算。这便是我们的第二种模式：&lt;strong&gt;I/O异步并行&lt;/strong&gt;，即同时开展多个I/O操作（也被称为overlapped I/O）。例如下面的代码便并行地请求多个Web页面，并响应每个请求的回复，再返回收集到的结果。 &lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Net
&lt;span style="color: blue"&gt;open &lt;/span&gt;Microsoft.FSharp.Control.WebExtensions
&lt;span style="color: blue"&gt;let &lt;/span&gt;http url =
    async { &lt;span style="color: blue"&gt;let &lt;/span&gt;req =  WebRequest.Create(Uri url)
            &lt;span style="color: blue"&gt;use! &lt;/span&gt;resp = req.AsyncGetResponse()
            &lt;span style="color: blue"&gt;use &lt;/span&gt;stream = resp.GetResponseStream()
            &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(stream)
            &lt;span style="color: blue"&gt;let &lt;/span&gt;contents = reader.ReadToEnd()
            &lt;span style="color: blue"&gt;return &lt;/span&gt;contents }
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;sites = [&lt;span style="color: maroon"&gt;&amp;quot;http://www.bing.com&amp;quot;&lt;/span&gt;;
             &lt;span style="color: maroon"&gt;&amp;quot;http://www.google.com&amp;quot;&lt;/span&gt;;
             &lt;span style="color: maroon"&gt;&amp;quot;http://www.yahoo.com&amp;quot;&lt;/span&gt;;
             &lt;span style="color: maroon"&gt;&amp;quot;http://www.search.com&amp;quot;&lt;/span&gt;]
 
&lt;span style="color: blue"&gt;let &lt;/span&gt;htmlOfSites =
    Async.Parallel [&lt;span style="color: blue"&gt;for &lt;/span&gt;site &lt;span style="color: blue"&gt;in &lt;/span&gt;sites &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;http site ]
    |&amp;gt; Async.RunSynchronously&lt;/pre&gt;

&lt;p&gt;上面的代码示例展示了I/O异步并行模式的基础： &lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;“async { … }”用于编写任务，其中包含了一些异步I/O。 &lt;/li&gt;

  &lt;li&gt;这些任务使用Async.Parallel进行fork-join式的组合。 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在这里，我们使用&lt;a href="http://msdn.microsoft.com/en-us/library/ee370262(VS.100).aspx"&gt;Async.RunSynchronously&lt;/a&gt;方法来执行组合后的任务，这会启动一个异步任务，并同步地等待其最后结果。 &lt;/p&gt;

&lt;p&gt;使用&lt;font color="#ff0000"&gt;let!&lt;/font&gt;（或与它类似的资源释放指令&lt;font color="#ff0000"&gt;use!&lt;/font&gt;）是进行异步操作的基础方法。例如：&lt;/p&gt;

&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let! &lt;/span&gt;resp = req.AsyncGetResponse()&lt;/pre&gt;

&lt;p&gt;上面这行代码会“响应”一个HTTP GET操作所得到的回复，即async { … }在AsyncGetResponse操作完成之后的部分。然而，在等待响应的过程中并不会阻塞任何.NET或操作系统的线程：只有活动的CPU密集型运算会使用下层的.NET或操作系统线程。与此不同，等待中的响应操作（例如回调函数，事件处理程序和代理对象）资源占用非常少，几乎只相当于一个注册好的对象而已。因此，您可以同时拥有数千个甚至数百万个等待中的响应操作。例如，一个典型的GUI应用程序会注册一些事件处理程序，而一个典型Web爬虫会为每个发出的请求注册一个回调函数。 &lt;/p&gt;

&lt;p&gt;在上面的代码中，我们使用了“use!”而不是“let!”，这表示Web请求相关的资源会在变量超出字面的作用域之后得到释放。 &lt;/p&gt;

&lt;p&gt;I/O并行的美妙之处在于其伸缩性。在多核的环境下，如果您可以充分利用计算资源，则通常会获得2倍、4倍甚至8倍的性能提高。而在I/O并行编程中，您可以同时进行成百上千的I/O操作（不过实际的并行效果还要取决于您的操作系统和网络连接状况），这意味着10倍、100倍、1000倍甚至更多的性能增强──而这一切在一台单核的机器上也可以实现。例如，这里有一个&lt;a href="http://blogs.msdn.com/nickhodge/archive/2008/11/12/ironpython-f-parallel-async-a-kittehz-brekfst.aspx"&gt;使用F#异步功能的示例&lt;/a&gt;，而最终它们可以在一个IronPython应用程序中使用。 &lt;/p&gt;

&lt;p&gt;许多现代应用程序都是I/O密集型应用，因此这些设计模式在实践中都有很重要的意义。 &lt;/p&gt;

&lt;h1&gt;始于GUI线程，终于GUI线程&lt;/h1&gt;

&lt;p&gt;这两个设计模式有个重要的变化，这便是使用&lt;a href="http://msdn.microsoft.com/en-us/library/ee370487(VS.100).aspx"&gt;Async.StartWithContinuations&lt;/a&gt;来代替&lt;a href="http://msdn.microsoft.com/en-us/library/ee370262(VS.100).aspx"&gt;Async.RunSynchronously&lt;/a&gt;方法。在一个并行操作开启之后，您可以指定三个函数，分别在它成功、失败或取消时调用。 &lt;/p&gt;

&lt;p&gt;对于诸如“我想要获得一个异步操作的结果，但我不能使用RunSynchronously方法”之类的问题，您便应该考虑： &lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;使用let!（或use!）把这个异步操作作为更大的异步任务的一部分，或者&lt;/li&gt;

  &lt;li&gt;使用Async.StartWithContinuations方法执行异步操作 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在那些需要在GUI线程上发起异步操作的场景中，&lt;a href="http://msdn.microsoft.com/en-us/library/ee370487(VS.100).aspx"&gt;Async.StartWithContinuations&lt;/a&gt;方法尤其有用。因为，您不会因此阻塞住GUI线程，而且可以在异步操作完成后直接进行GUI的更新。例如，在&lt;a href="http://blogs.msdn.com/dsyme/archive/2009/10/10/f-tutorial-code-and-slides-jaoo-2009-edition.aspx"&gt;F# JAOO Tutorial&lt;/a&gt;的&lt;a href="http://www.microsofttranslator.com/"&gt;BingTranslator&lt;/a&gt;示例中便使用了这个做法──您可以在本文结尾浏览它的完整代码，不过这里最值得关注的部分则是在点击“Translate”按钮之后发生的事情： &lt;/p&gt;

&lt;pre class="code"&gt;button.Click.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;args &lt;span style="color: blue"&gt;-&amp;gt;
 
    let &lt;/span&gt;text = textBox.Text
    translated.Text &amp;lt;- &lt;span style="color: maroon"&gt;&amp;quot;Translating...&amp;quot;
 
    &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;task =
        async { &lt;span style="color: blue"&gt;let! &lt;/span&gt;languages = httpLines languageUri
                &lt;span style="color: blue"&gt;let! &lt;/span&gt;fromLang = detectLanguage text
                &lt;span style="color: blue"&gt;let! &lt;/span&gt;results = &lt;span style="background-color: yellow"&gt;Async.Parallel [&lt;span style="color: blue"&gt;for &lt;/span&gt;lang &lt;span style="color: blue"&gt;in &lt;/span&gt;languages &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;translateText (text, fromLang, lang)]&lt;/span&gt;
                &lt;span style="color: blue"&gt;return &lt;/span&gt;(fromLang,results) }
 
    Async.StartWithContinuations( 
        task,
        (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(fromLang,results) &lt;span style="color: blue"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span style="background-color: yellow"&gt;&lt;span style="color: blue"&gt;for &lt;/span&gt;(toLang, translatedText) &lt;span style="color: blue"&gt;in &lt;/span&gt;results &lt;span style="color: blue"&gt;do
                &lt;/span&gt;translated.Text &amp;lt;- translated.Text + sprintf &lt;span style="color: maroon"&gt;&amp;quot;\r\n%s --&amp;gt; %s: \&amp;quot;%s\&amp;quot;&amp;quot; &lt;/span&gt;fromLang toLang translatedText),
        (&lt;span style="color: blue"&gt;fun &lt;/span&gt;exn &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;MessageBox.Show(sprintf &lt;span style="color: maroon"&gt;&amp;quot;An error occurred: %A&amp;quot; &lt;/span&gt;exn) |&amp;gt; ignore),&lt;/span&gt;
        (&lt;span style="color: blue"&gt;fun &lt;/span&gt;cxn &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;MessageBox.Show(sprintf &lt;span style="color: maroon"&gt;&amp;quot;A cancellation error ocurred: %A&amp;quot; &lt;/span&gt;cxn) |&amp;gt; ignore)))&lt;/pre&gt;

&lt;p&gt;高亮的部分，尤其是在async块里的部分，展示了使用Async.Parallel将一种语言并行地翻译成多种语言的做法。这个异步组合操作由Async.StartWithContinuations发起，它会在遇到第一个I/O操作时立即返回（译注：存疑，为什么是在遇上I/O操作才返回？），并指定了三个函数，分别在异步操作的成功，失败或取消时调用。以下是任务完成后的截图（不过在此不保证翻译的准确性……）： &lt;/p&gt;

&lt;a href="http://img.zhaojie.me/blog/FSharp-Async-BingTranslator.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/FSharp-Async-BingTranslator.png" width="400px;" /&gt;&lt;/a&gt;

&lt;p&gt;Async.StartWithContinuations有一个重要的特性：如果异步操作由GUI线程发起（例如一个SynchronizationContext.Current不为null的线程），那么操作完成后的回调函数也是在GUI线程中调用的。这使GUI更新操作变的十分安全。F#异步类库允许您组合多个I/O任务，并在GUI线程中直接使用，而无需您亲自从后台线程中更新GUI元素。在以后的文章中我们会进行更详细地解释。 &lt;/p&gt;

&lt;p&gt;关于Async.Parallel工作方式： &lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;在执行时，由Async.Parallel组合而成的异步操作会通过一个等待计算的队列来逐步发起。与大部分进行异步处理的类库一样，它在内部使用的是QueueUserWorkItem方法。当然，我们也有办法使用分离的队列，在以后的文章中我们会进行一些讨论。 &lt;/li&gt;

  &lt;li&gt;Async.Parallel方法并没有什么神奇之处，您也完全可以使用Microsoft.FSharp.Control.Async类库中的其他原语来定义您自己的异步组合方式──例如Async.StartChild方法。我们会在以后的文章中讨论这个话题。 &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;更多示例&lt;/h1&gt;

&lt;p&gt;在&lt;a href="http://blogs.msdn.com/dsyme/archive/2009/10/10/f-tutorial-code-and-slides-jaoo-2009-edition.aspx"&gt;F# JAOO Tutorial&lt;/a&gt;包含多个使用这些模式的示例代码： &lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;BingTranslator.fsx与BingTranslatorShort.fsx：使用F#调用REST API，它们与其他基于Web的HTTP服务的调用方式十分类似。文末包含了示例的完整代码。 &lt;/li&gt;

  &lt;li&gt;AsyncImages.fsx：并行磁盘I/O及图像处理。 &lt;/li&gt;

  &lt;li&gt;PeriodicTable.fsx：调用一个Web服务，并行地获取原子质量。 &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;本文模式的限制&lt;/h1&gt;

&lt;p&gt;上文介绍的两个并行模式有一些限制。很明显，使用Async.Parallel生成的异步操作在执行时十分“安静”──比方说，它们无法返回进度或部分的结果。为此，我们需要构建一个更为“丰富”的对象，它会在部分操作完成之后触发一些事件。在以后的文章中我们会来关注这样的设计模式。 &lt;/p&gt;

&lt;p&gt;此外，Async.Parallel只能处理固定数量的任务。在以后的文章中，我们会遇到很多一边处理一边生成任务的情况。换个方式来看，即Async.Parallel无法处理即时获得的消息──例如，除了取消任务之外，一个代理对象的工作进度是可以得到控制的。 &lt;/p&gt;

&lt;h1&gt;总结&lt;/h1&gt;

&lt;p&gt;CPU异步并行与I/O异步并行，是F#异步编程中最为简单的两种设计模式，而简单的事物往往也是非常重要而强大的。请注意，两种模式的不同之处，仅仅在于I/O并行使用了包含了I/O请求的async块，以及一些额外的CPU任务，如创建请求对象及后续处理。 &lt;/p&gt;

&lt;p&gt;在今后的文章里，我们会关注F#中其他一些并行及响应式编程方面的设计方式，包括： &lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;从GUI线程中发起异步操作 &lt;/li&gt;

  &lt;li&gt;定义轻量级异步代理对象&lt;/li&gt;

  &lt;li&gt;使用async定义后台工作程序 &lt;/li&gt;

  &lt;li&gt;使用async构建.NET任务 &lt;/li&gt;

  &lt;li&gt;使用async调用.NET的APM模式 &lt;/li&gt;

  &lt;li&gt;取消异步操作 &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;BingTranslator代码示例&lt;/h1&gt;

&lt;p&gt;以下是BingTranslator的示例代码，在运行时您需要申请一个&lt;a href="http://www.bing.com/developers"&gt;Live API 1.1 AppID&lt;/a&gt;。请注意，这个示例需要根据&lt;a href="http://msdn.microsoft.com/en-us/library/dd251056.aspx"&gt;Bing API 2.0&lt;/a&gt;进行适当调整，至少在2.0中已经不包含这里的语言检测API了──不过这些代码仍然是不错的示例：&lt;/p&gt;

&lt;pre class="code" id="_code_BingTranslator_hide_"&gt;&lt;a href="javascript:__showAndHide('_code_BingTranslator_show_', '_code_BingTranslator_hide_');"&gt;点此展开&lt;/a&gt;&lt;/pre&gt;

&lt;pre class="code" id="_code_BingTranslator_show_" style="display:none;"&gt;&lt;a href="javascript:__showAndHide('_code_BingTranslator_hide_', '_code_BingTranslator_show_');"&gt;点此隐藏&lt;/a&gt;

&lt;span style="color: blue"&gt;open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Net
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.IO
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Drawing
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Windows.Forms
&lt;span style="color: blue"&gt;open &lt;/span&gt;System.Text
 
&lt;span style="color: green"&gt;/// A standard helper to read all the lines of a HTTP request. The actual read of the lines is
/// synchronous once the HTTP response has been received.
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;httpLines (uri:string) =
  async { &lt;span style="color: blue"&gt;let &lt;/span&gt;request = WebRequest.Create uri
          &lt;span style="color: blue"&gt;use! &lt;/span&gt;response = request.AsyncGetResponse()         
          &lt;span style="color: blue"&gt;use &lt;/span&gt;stream = response.GetResponseStream()
          &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(stream)
          &lt;span style="color: blue"&gt;let &lt;/span&gt;lines = [ &lt;span style="color: blue"&gt;while &lt;/span&gt;not reader.EndOfStream &lt;span style="color: blue"&gt;do yield &lt;/span&gt;reader.ReadLine() ]
          &lt;span style="color: blue"&gt;return &lt;/span&gt;lines }
 
&lt;span style="color: blue"&gt;type &lt;/span&gt;System.Net.WebRequest &lt;span style="color: blue"&gt;with
 
    &lt;/span&gt;&lt;span style="color: green"&gt;/// An extension member to write content into an WebRequest.
    /// The write of the content is synchronous.
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;req.WriteContent (content:string) =
        &lt;span style="color: blue"&gt;let &lt;/span&gt;bytes = Encoding.UTF8.GetBytes content
        req.ContentLength &amp;lt;- int64 bytes.Length
        &lt;span style="color: blue"&gt;use &lt;/span&gt;stream = req.GetRequestStream()
        stream.Write(bytes,0,bytes.Length)
 
    &lt;span style="color: green"&gt;/// An extension member to read the content from a response to a WebRequest.
    /// The read of the content is synchronous once the response has been received.
    &lt;/span&gt;&lt;span style="color: blue"&gt;member &lt;/span&gt;req.AsyncReadResponse () =
        async { &lt;span style="color: blue"&gt;use! &lt;/span&gt;response = req.AsyncGetResponse()
                &lt;span style="color: blue"&gt;use &lt;/span&gt;responseStream = response.GetResponseStream()
                &lt;span style="color: blue"&gt;use &lt;/span&gt;reader = &lt;span style="color: blue"&gt;new &lt;/span&gt;StreamReader(responseStream)
                &lt;span style="color: blue"&gt;return &lt;/span&gt;reader.ReadToEnd() }
 
#load &lt;span style="color: maroon"&gt;&amp;#64;&amp;quot;C:\fsharp\staging\docs\presentations\2009-10-04-jaoo-tutorial\BingAppId.fs&amp;quot;
&lt;/span&gt;&lt;span style="color: green"&gt;//let myAppId = &amp;quot;please set your Bing AppId here&amp;quot;
 
 
/// The URIs for the REST service we are using
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;detectUri       = &lt;span style="color: maroon"&gt;&amp;quot;http://api.microsofttranslator.com/V1/Http.svc/Detect?appId=&amp;quot; &lt;/span&gt;+ myAppId
&lt;span style="color: blue"&gt;let &lt;/span&gt;translateUri    = &lt;span style="color: maroon"&gt;&amp;quot;http://api.microsofttranslator.com/V1/Http.svc/Translate?appId=&amp;quot; &lt;/span&gt;+ myAppId + &lt;span style="color: maroon"&gt;&amp;quot;&amp;amp;&amp;quot;
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;languageUri     = &lt;span style="color: maroon"&gt;&amp;quot;http://api.microsofttranslator.com/V1/Http.svc/GetLanguages?appId=&amp;quot; &lt;/span&gt;+ myAppId
&lt;span style="color: blue"&gt;let &lt;/span&gt;languageNameUri = &lt;span style="color: maroon"&gt;&amp;quot;http://api.microsofttranslator.com/V1/Http.svc/GetLanguageNames?appId=&amp;quot; &lt;/span&gt;+ myAppId
 
&lt;span style="color: green"&gt;/// Create the user interface elements
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;form       = &lt;span style="color: blue"&gt;new &lt;/span&gt;Form (Visible=&lt;span style="color: blue"&gt;true&lt;/span&gt;, TopMost=&lt;span style="color: blue"&gt;true&lt;/span&gt;, Height=500, Width=600)
&lt;span style="color: blue"&gt;let &lt;/span&gt;textBox    = &lt;span style="color: blue"&gt;new &lt;/span&gt;TextBox (Width=450, Text=&lt;span style="color: maroon"&gt;&amp;quot;Enter some text&amp;quot;&lt;/span&gt;, Font=&lt;span style="color: blue"&gt;new &lt;/span&gt;Font(&lt;span style="color: maroon"&gt;&amp;quot;Consolas&amp;quot;&lt;/span&gt;, 14.0F))
&lt;span style="color: blue"&gt;let &lt;/span&gt;button     = &lt;span style="color: blue"&gt;new &lt;/span&gt;Button (Text=&lt;span style="color: maroon"&gt;&amp;quot;Translate&amp;quot;&lt;/span&gt;, Left = 460)
&lt;span style="color: blue"&gt;let &lt;/span&gt;translated = &lt;span style="color: blue"&gt;new &lt;/span&gt;TextBox (Width = 590, Height = 400, Top = 50, ScrollBars = ScrollBars.Both, Multiline = &lt;span style="color: blue"&gt;true&lt;/span&gt;, Font=&lt;span style="color: blue"&gt;new &lt;/span&gt;Font(&lt;span style="color: maroon"&gt;&amp;quot;Consolas&amp;quot;&lt;/span&gt;, 14.0F))
 
form.Controls.Add textBox
form.Controls.Add button
form.Controls.Add translated
 
 
&lt;span style="color: green"&gt;/// An async method to call the language detection API
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;detectLanguage text =
  async { &lt;span style="color: blue"&gt;let &lt;/span&gt;request = WebRequest.Create (detectUri, Method=&lt;span style="color: maroon"&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;, ContentType=&lt;span style="color: maroon"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;)
          &lt;span style="color: blue"&gt;do &lt;/span&gt;request.WriteContent text
          &lt;span style="color: blue"&gt;return! &lt;/span&gt;request.AsyncReadResponse() }
 
&lt;span style="color: green"&gt;/// An async method to call the text translation API
&lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;translateText (text, fromLang, toLang) =
  async { &lt;span style="color: blue"&gt;let &lt;/span&gt;uri = sprintf &lt;span style="color: maroon"&gt;&amp;quot;%sfrom=%s&amp;amp;to=%s&amp;quot; &lt;/span&gt;translateUri fromLang toLang
          &lt;span style="color: blue"&gt;let &lt;/span&gt;request = WebRequest.Create (uri, Method=&lt;span style="color: maroon"&gt;&amp;quot;Post&amp;quot;&lt;/span&gt;, ContentType=&lt;span style="color: maroon"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;)
          request.WriteContent text
          &lt;span style="color: blue"&gt;let! &lt;/span&gt;translatedText = request.AsyncReadResponse()
          &lt;span style="color: blue"&gt;return &lt;/span&gt;(toLang, translatedText) }
 
button.Click.Add(&lt;span style="color: blue"&gt;fun &lt;/span&gt;args &lt;span style="color: blue"&gt;-&amp;gt;
 
    let &lt;/span&gt;text = textBox.Text
    translated.Text &amp;lt;- &lt;span style="color: maroon"&gt;&amp;quot;Translating...&amp;quot;
 
    &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;task =
        async { &lt;span style="color: green"&gt;/// Get the supported languages
                &lt;/span&gt;&lt;span style="color: blue"&gt;let! &lt;/span&gt;languages = httpLines languageUri
                &lt;span style="color: green"&gt;/// Detect the language of the input text. This could be done in parallel with the previous step.
                &lt;/span&gt;&lt;span style="color: blue"&gt;let! &lt;/span&gt;fromLang = detectLanguage text
                &lt;span style="color: green"&gt;/// Translate into each language, in parallel
                &lt;/span&gt;&lt;span style="color: blue"&gt;let! &lt;/span&gt;results = Async.Parallel [&lt;span style="color: blue"&gt;for &lt;/span&gt;lang &lt;span style="color: blue"&gt;in &lt;/span&gt;languages &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;translateText (text, fromLang, lang)]
                &lt;span style="color: green"&gt;/// Return the results
                &lt;/span&gt;&lt;span style="color: blue"&gt;return &lt;/span&gt;(fromLang,results) }
 
    &lt;span style="color: green"&gt;/// Start the task. When it completes, show the results.
    &lt;/span&gt;Async.StartWithContinuations( 
        task,
        (&lt;span style="color: blue"&gt;fun &lt;/span&gt;(fromLang,results) &lt;span style="color: blue"&gt;-&amp;gt;
            for &lt;/span&gt;(toLang, translatedText) &lt;span style="color: blue"&gt;in &lt;/span&gt;results &lt;span style="color: blue"&gt;do
                &lt;/span&gt;translated.Text &amp;lt;- translated.Text + sprintf &lt;span style="color: maroon"&gt;&amp;quot;\r\n%s --&amp;gt; %s: \&amp;quot;%s\&amp;quot;&amp;quot; &lt;/span&gt;fromLang toLang translatedText),
        (&lt;span style="color: blue"&gt;fun &lt;/span&gt;exn &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;MessageBox.Show(sprintf &lt;span style="color: maroon"&gt;&amp;quot;An error occurred: %A&amp;quot; &lt;/span&gt;exn) |&amp;gt; ignore),
        (&lt;span style="color: blue"&gt;fun &lt;/span&gt;cxn &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;MessageBox.Show(sprintf &lt;span style="color: maroon"&gt;&amp;quot;A cancellation error ocurred: %A&amp;quot; &lt;/span&gt;cxn) |&amp;gt; ignore)))&lt;/pre&gt;

&lt;p&gt;原文：&lt;a href="http://blogs.msdn.com/dsyme/archive/2010/01/09/async-and-parallel-design-patterns-in-f-parallelizing-cpu-and-i-o-computations.aspx"&gt;Async and Parallel Design Patterns in F#: Parallelizing CPU and I/O Computations&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2010/03/async-and-parallel-design-patterns-in-fsharp-1-parallelizing-cpu-and-io-computations.html#comments</comments>
      <pubDate>Wed, 03 Mar 2010 15:37:00 GMT</pubDate>
      <lastBuildDate>Wed, 03 Mar 2010 15:37:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/speech/">培训演讲</category>
      <title>视频：Microsoft PDC 09，算法及数据结构内容及其他</title>
      <link>http://blog.zhaojie.me/2009/11/videos-of-pdc09-algorithms-data-structure-visual-studio-documentary.html</link>
      <guid>http://blog.zhaojie.me/2009/11/videos-of-pdc09-algorithms-data-structure-visual-studio-documentary.html</guid>
      <description>&lt;p&gt;这里又有一些新整理好的视频。Microsoft PDC 09是最近的重头，只要您是搞微软技术的，无论关注哪个技术方面，都可以找到许多有用的内容。我也经常从此类大会中了解许多平时不太关注的内容，也算是保持知识的新鲜度。此外，还有算法和数据结构相关的内容，以及有趣的Visual Studio纪录片。&lt;/p&gt;  &lt;h1&gt;Microsoft PDC 09&lt;/h1&gt; &lt;img class="floatRight" src="http://img.zhaojie.me/blog/168980/o_PDC09Bling_BeforeAfter_136.jpg" /&gt;   &lt;p&gt;PDC，也就是Professional Developer Conference，专业开发者会议。这是微软的又一个技术大会，而且应该是个真正的技术大会。我为什么这么说呢？一个重要方面便是讲师的身份。PDC的讲师，大都是各微软产品团队的开发人员和设计人员，甚至一些科研人员，包括许多Technical Fellow和Distinguished Engineer等神仙级别的人物。总的来说，这个会议是来搞技术的。&lt;/p&gt;  &lt;p&gt;与此相对的一个典型是TechEd，它的定位是技术推广，让各厂商、合作伙伴了解微软技术、产品发展到什么样子了。因此，它的讲师大都是微软的咨询师，传教士等技术传播人员，而往往不是真正的技术开发者（当然我并不是说他们就不懂技术了）。从课程难度和深度上来说，TechEd大会的课程基本上都不能超过200，都应该属于介绍性质的，而PDC的内容，一般都要是300及以上了。&lt;/p&gt;  &lt;p&gt;因此我的观点就是，如果您专注于技术，那么不妨多专注于PDC或&lt;a href="http://visitmix.com/"&gt;MIX&lt;/a&gt;等技术会议，而不要在TechEd上追求太多——除非您已是高管，那么TechEd可以给您更凝练的感受。&lt;/p&gt;  &lt;p&gt;一星期前微软在洛杉矶举办了&lt;a href="http://microsoftpdc.com"&gt;PDC 09&lt;/a&gt;大会，然后放出了全部视频。然后经过了几天的下载和上传，我也终于把它们都放到优库上去了。如果您和我一样，在国内直接看PDC网站上的视频很慢，那么不妨可以看看优库上的版本。当然，您也可以选择去大会网站上下载高清版本，或低清版——还是比优库要清晰一些的。优库的好处在于想看便能立即看到，而且可以随意拖动。虽然不太清晰，但如果是需要让您看清楚的代码，讲师都会把它们放大，所以基本上也没有太大问题。&lt;/p&gt;  &lt;p&gt;这次的PDC的Session总共分七大部分，我将它们分为七个专辑：&lt;/p&gt;  &lt;ul&gt;&lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3966249.html"&gt;Keynotes (KEY)&lt;/a&gt; &lt;/li&gt;   &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3956816.html"&gt;Client (CL)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3959023.html"&gt;Frameworks &amp;amp; Tools (FT)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3960432.html"&gt;Productivity (PR)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3961503.html"&gt;Server (SVR)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3961304.html"&gt;Services (SVC)&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3962400.html"&gt;Virtual&amp;#160; (VTL)&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;由于视频太多，我不可能一个个地整理，因此目前我只为那些我看过的或是感兴趣的（看了一部分的）的视频填写的信息。不过每个视频的编号（如FT09）都是完整且排好序的，您可以先去&lt;a href="http://microsoftpdc.com/videos"&gt;大会的视频列表&lt;/a&gt;中找到感兴趣的话题，然后再去优库上看。&lt;/p&gt;  &lt;p&gt;如果您看完了某个视频，我建议可以写一点总结，然后和大家一起分享，例如我目前已经写了《&lt;a href="http://www.infoq.com/cn/news/2009/11/pdc09-fsharp"&gt;并行和异步编程中的挑战及F#的应对方案&lt;/a&gt;》及《&lt;a href="http://www.infoq.com/cn/news/2009/11/pdc09-aspnet4"&gt;ASP.NET 4运行时的改进&lt;/a&gt;》两篇总结，接下来也打算总结更多内容。&lt;/p&gt;  &lt;h1&gt;算法及数据结构及其他&lt;/h1&gt;  &lt;p&gt;今天在Reddit上看到有人贴了一篇文章：&lt;a href="http://techbytes360.blogspot.com/2009/11/free-algorithms-and-data-structures.html"&gt;Top Algorithms and Data Structures Video Lectures&lt;/a&gt;，其中列出了：&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Algorithms&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-046JFall-2005/VideoLectures/index.htm"&gt;MIT OpenCourseWare: Introduction to Algorithms&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://nptel.iitm.ac.in/video.php?courseId=1065"&gt;NPTEL: Design and Analysis of Algorithm&lt;/a&gt; (by IIT and IISC, India) &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.cs.sunysb.edu/%7Ealgorith/video-lectures/"&gt;Stony Brook University: Skiena's Algorithms Lectures&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.aduni.org/courses/algorithms/index.php?view=cw"&gt;ArsDigita University: Algorithms&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&lt;strong&gt;Data Structures&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://webcast.berkeley.edu/course_details.php?seriesid=1906978271"&gt;UC Berkeley: Data Structures&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://nptel.iitm.ac.in/video.php?courseId=1074"&gt;NPTEL: Data Structures And Algorithms&lt;/a&gt; (by IIT and IISC, India)&amp;#160; &lt;/li&gt;    &lt;li&gt;&lt;a href="https://wiki.cse.unsw.edu.au/cs1927cgi/09s2/Schedule"&gt;The University of New South Wales: Data Structures and Algorithms&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;而我发现，对于最负盛名的两所学校，MIT和UCB的算法与数据结构的课程，也已经有人整理在优库上了：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3967498.html"&gt;MIT: Introduction to Algorithms&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.youku.com/playlist_show/id_3763991.html"&gt;UC Berkeley: Data Structures&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;看到那么多学习资源，您还觉得哪里不满足吗？只是，看来学好英语是非常重要的——我现在越来越认同“大学毕业生必须过英语四级”这个硬性规定，只可惜我见过太多蒙混过关的人了……而且，很多人还觉得这是他们的幸运。这其实也挺悲哀的。&lt;/p&gt;  &lt;p&gt;最后，如果您对Visual Studio的历史感兴趣的话，Channel 9上最近在连载Visual Studio的纪录片，我也在优库上&lt;a href="http://www.youku.com/playlist_show/id_3963343.html"&gt;保持更新&lt;/a&gt;，除了技术内容至于，看这类内容也是挺有趣味的。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/11/videos-of-pdc09-algorithms-data-structure-visual-studio-documentary.html#comments</comments>
      <pubDate>Fri, 27 Nov 2009 05:57:00 GMT</pubDate>
      <lastBuildDate>Fri, 27 Nov 2009 05:57:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>浅谈线程池（下）：相关试验及注意事项</title>
      <link>http://blog.zhaojie.me/2009/10/thread-pool-3-lab.html</link>
      <guid>http://blog.zhaojie.me/2009/10/thread-pool-3-lab.html</guid>
      <description>&lt;p&gt;三个月，整整三个月了，我忽然发现我还有三个月前的一个小系列的文章没有结束，我还欠一个试验！线程池是.NET中的重要组件，几乎所有的异步功能依赖于线程池。之前我们讨论了线程池的作用、独立线程池的存在意义，以及对CLR线程池和IO线程池进行了一定说明。不过这些说明可能有些“抽象”，于是我们还是要通过试验来“验证”这些说明。此外，我认为针对某个“猜想”来设计一些试验进行验证是非常重要的能力，如果您这方面的能力略有不足的话，还是尽量加以锻炼并提高吧。&lt;/p&gt; &lt;h1&gt;CLR线程的使用与创建&lt;/h1&gt; &lt;p&gt;首先，我们准备这样一段代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static void &lt;/span&gt;ThreadUseAndConstruction()
{
    &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.SetMinThreads(5, 5); &lt;span style="color: green"&gt;// set min thread to 5
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.SetMaxThreads(12, 12); &lt;span style="color: green"&gt;// set max thread to 12

    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch &lt;/span&gt;watch = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&lt;/span&gt;();
    watch.Start();

    &lt;span style="color: #2b91af"&gt;WaitCallback &lt;/span&gt;callback = index =&amp;gt;
    {
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;"{0}: Task {1} started"&lt;/span&gt;, watch.Elapsed, index));
        &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(10000);
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;"{0}: Task {1} finished"&lt;/span&gt;, watch.Elapsed, index));
    };

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 20; i++)
    {
        &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.QueueUserWorkItem(callback, i);
    }
}&lt;/pre&gt;
&lt;p&gt;这段代码很简单。首先将线程池最小和最大线程数量设为5和12，然后向线程池中连续推入20个任务，每个任务都是打印出执行时的当前时间，然后等待10秒钟。那么请您思考一下，这段代码的输出是什么样的呢？&lt;/p&gt;&lt;pre id="toHide-code-1" class="code"&gt;&lt;a href="javascript:__showAndHide('toShow-code-1', 'toHide-code-1');"&gt;展开&lt;/a&gt;&lt;/pre&gt;&lt;pre style="display: none" id="toShow-code-1" class="code"&gt;&lt;a href="javascript:__showAndHide('toHide-code-1', 'toShow-code-1');"&gt;折叠&lt;/a&gt;

00:00:00.0028309: Task 0 started
00:00:00.0079552: Task 1 started
00:00:00.0080033: Task 2 started
00:00:00.0081628: Task 3 started
00:00:01.0058442: Task 4 started
00:00:01.5039911: Task 5 started
00:00:02.0048392: Task 6 started
00:00:02.5051786: Task 7 started
00:00:03.0051154: Task 8 started
00:00:04.0048998: Task 9 started
00:00:05.0053109: Task 10 started
00:00:06.0068503: Task 11 started
00:00:10.0079897: Task 1 finished
00:00:10.0084587: Task 12 started
00:00:10.0087316: Task 2 finished
00:00:10.0079939: Task 3 finished
00:00:10.0090849: Task 14 started
00:00:10.0088292: Task 0 finished
00:00:10.0101870: Task 15 started
00:00:10.0089327: Task 13 started
00:00:11.0059534: Task 4 finished
00:00:11.0063235: Task 16 started
00:00:11.5040658: Task 5 finished
00:00:11.5044820: Task 17 started
00:00:12.0051271: Task 6 finished
00:00:12.0064219: Task 18 started
00:00:12.5061198: Task 7 finished
00:00:12.5074655: Task 19 started
00:00:13.0052512: Task 8 finished
00:00:14.0052185: Task 9 finished
00:00:15.0064023: Task 10 finished
00:00:16.0074270: Task 11 finished
00:00:20.0085632: Task 12 finished
00:00:20.0094829: Task 14 finished
00:00:20.0104810: Task 13 finished
00:00:20.0119368: Task 15 finished
00:00:21.0066450: Task 16 finished
00:00:21.5045454: Task 17 finished
00:00:22.0116638: Task 18 finished
00:00:22.5107089: Task 19 finished&lt;/pre&gt;
&lt;p&gt;高位的零我们就直接忽略了，我们只观察“秒”及以下精度的时间。对这个数据进行简单观察之后，我们发现可以把时间精确到0.5秒来描述每个时刻所发生的事情：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;0秒：任务0至任务3，共计4个任务开始执行。 
&lt;li&gt;1至3秒：任务4至任务8依次执行，间隔为0.5秒。 
&lt;li&gt;3至6秒：任务8至任务11依次执行，间隔为1秒。 
&lt;li&gt;10秒：任务0至任务3执行完成，任务12至任务15开始执行。 
&lt;li&gt;11至12.5秒：每执行完一个旧任务（4至7)，便立即开始一个新任务（16至19）。 
&lt;li&gt;13至22.5秒：剩余任务（8至19）依次结束。　　&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;您猜对了吗？我没有猜对，因为有两点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原来最小线程数量为5时，只有4个线程可以立即执行。经过进一步尝试，最小线程数量为10时，也只有9个线程可以立即执行。 
&lt;li&gt;原来线程池创建线程的速度并非永远是“每秒2个”，而一些资料上写着“每秒不超过2个”的确是确切的说法。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;但是，我们还是验证了以下几个结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在线程池最小线程数量的范围之内，尽可能多的任务立即执行。 
&lt;li&gt;线程池使用使用每秒不超过2个的频率创建线程（1秒一个或0.5秒一个）。 
&lt;li&gt;当达到线程池最大线程数时（第6秒），停止创建新线程。 
&lt;li&gt;在旧任务执行完毕后，新任务立即执行。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;当然，由于我们在这之前已经“了解”了线程池是如何工作的，因此这里得到的结果可能会有“自圆其说”的倾向在里面。要减少这个可能性，则需要设计更完整的试验来“解释”问题。您也可以顺着这一点进行更深入的探索。&lt;/p&gt;
&lt;h1&gt;线程池中的线程是“公用”的&lt;/h1&gt;
&lt;p&gt;我们没有独立创建线程，而是选择使用线程池一定有其原因。不过，我们既然使用了线程池，就有一些额外的东西值得注意。 
&lt;p&gt;首先，我们要明确一个观念：线程并不“属于”任何一个任务，或者说任务并不“拥有”线程。我们只是借用一个线程来做事，用完以后便会还回。也就是说，任务在执行时修改线程的信息（名称，优先级，语言文化等等）是没有意义的，此外，任务也不应该依赖线程的这些状态。还记得上篇文章中谈到的QueueUserWorkItem和UnsafeQueueUserWorkItem之间的区别吗？如果您的任务需要依赖什么东西，也请自行准备。线程池中的线程状态是不可靠的。当然，也尽量不要直接对当前线程进行其他操作。 
&lt;p&gt;其次，由于线程池有大小限制，在某些时候还可能出现死锁的情况：&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;WaitCallback(&lt;span style="color: blue"&gt;object &lt;/span&gt;handle)
{
    &lt;span style="color: #2b91af"&gt;ManualResetEvent &lt;/span&gt;waitHandle = (&lt;span style="color: #2b91af"&gt;ManualResetEvent&lt;/span&gt;)handle;

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 10; i++)
    {
        &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.QueueUserWorkItem(state =&amp;gt;
        {
            &lt;span style="color: blue"&gt;int &lt;/span&gt;index = (&lt;span style="color: blue"&gt;int&lt;/span&gt;)state;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(index == 9)
            {
                waitHandle.Set(); &lt;span style="color: green"&gt;// release all &lt;/span&gt;
            }
            &lt;span style="color: blue"&gt;else
            &lt;/span&gt;{
                waitHandle.WaitOne(); &lt;span style="color: green"&gt;// wait &lt;/span&gt;
            }
        }, i);
    }
}

&lt;span style="color: blue"&gt;public static void &lt;/span&gt;DeadLock()
{
    &lt;span style="color: #2b91af"&gt;ManualResetEvent &lt;/span&gt;waitHandle = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ManualResetEvent&lt;/span&gt;(&lt;span style="color: blue"&gt;false&lt;/span&gt;);

    &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.SetMaxThreads(5, 5);
    &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.QueueUserWorkItem(WaitCallback, waitHandle);

    waitHandle.WaitOne();
}&lt;/pre&gt;
&lt;p&gt;在上面的代码中，waitHandle将永远阻塞。因为我们放入线程池的10个任务，只有最后一个会将waitHandle打开，其余任务也统统阻塞在这个waitHandle上。但是请注意，我们使用SetMaxThreads方法把最大线程数限制为5，这样第10个任务根本无法执行，从而进入了死锁。避免这个问题最简单的做法是增加最大线程数，但是这还是会产生许多无法工作的线程，造成资源的浪费。因此，最好的做法是重新设计并行算法，并且时刻记住：“不要阻塞线程池里的线程”。 
&lt;p&gt;如何合理而有效的使用线程（既不多也不少还不阻塞），这是并行算法中最常见的课题之一。例如，让您设计一个并行计算&lt;a href="http://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97"&gt;斐波那契数列&lt;/a&gt;的算法，如果您每次计算Fib(n)时，都创建两个新的任务来并行计算Fib(n - 1)和Fib(n - 2)，并等待它们结束，就会造成上述的死锁（或大量线程）。如何解决这个问题？您可以观察一下.NET 4.0中新增的Task并行类库，它提供了丰富而易用的并行运算API，帮我们省去了大量的工作&lt;sup&gt;1&lt;/sup&gt;。 
&lt;p&gt;最后，便是时刻记得系统中哪些功能依赖线程池。例如ASP.NET中的请求也会使用CLR线程池，那么您是否应该使用ThreadPool？是否应该直接使用委托的异步调用？您是否应该调整线程池的最大和最小线数？这些问题没有确定答案，这需要您根据实际情况自己做判断。 
&lt;h1&gt;CLR线程池与IO线程池&lt;/h1&gt;
&lt;p&gt;当第一次了解到.NET准备了一个CLR线程池和一个IO线程池的时后，我在想，这两者真的是没有关系的吗？他们会互相影响吗？于是我做了这么一个试验：&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static void &lt;/span&gt;IoThread()
{
    &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.SetMinThreads(5, 3);
    &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.SetMaxThreads(5, 3);

    &lt;span style="color: #2b91af"&gt;ManualResetEvent &lt;/span&gt;waitHandle = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ManualResetEvent&lt;/span&gt;(&lt;span style="color: blue"&gt;false&lt;/span&gt;);

    &lt;span style="color: #2b91af"&gt;Stopwatch &lt;/span&gt;watch = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&lt;/span&gt;();
    watch.Start();

    &lt;span style="color: #2b91af"&gt;WebRequest &lt;/span&gt;request = &lt;span style="color: #2b91af"&gt;HttpWebRequest&lt;/span&gt;.Create(&lt;span style="color: #a31515"&gt;"http://www.cnblogs.com/"&lt;/span&gt;);
    request.BeginGetResponse(ar =&amp;gt;
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;response = request.EndGetResponse(ar);
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(watch.Elapsed + &lt;span style="color: #a31515"&gt;": Response Get"&lt;/span&gt;);

    }, &lt;span style="color: blue"&gt;null&lt;/span&gt;);

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 10; i++)
    {
        &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.QueueUserWorkItem(index =&amp;gt;
        {
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #2b91af"&gt;String&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;"{0}: Task {1} started"&lt;/span&gt;, watch.Elapsed, index));
            waitHandle.WaitOne();

        }, i);
    }

    waitHandle.WaitOne();
}&lt;/pre&gt;
&lt;p&gt;得到的结果是这样的：&lt;/p&gt;&lt;pre class="code"&gt;00:00:00.0923543: Task 0 started
00:00:00.1152495: Task 2 started
00:00:00.1153073: Task 3 started
00:00:00.1152439: Task 1 started
00:00:01.0976629: Task 4 started
00:00:01.5235481: Response Get&lt;/pre&gt;
&lt;p&gt;从中可以看出，我们将CLR线程池的最大线程数量设为了5，并使用与上一例类似的做法故意“阻塞”了线程池（而只有5个任务被执行了，说明线程池的确被阻塞了），其目的便是观察在这种情况下一个IO异步请求是否能够得到正确的回复。答案是肯定的，IO异步请求的回调函数正常执行了。这意味着，虽然CLR线程池被用完了，但是似乎的确还是有一个额外的IO线程池在处理IO的异步回调。这样看来，CLR线程池和IO线程池两者并没有影响。此外，从.NET框架所设计的类库来看，的确将两者作了区分，例如：&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt; 
{
    &lt;span style="color: blue"&gt;public static bool &lt;/span&gt;GetAvailableThreads(&lt;span style="color: blue"&gt;out int&lt;/span&gt; workerThreads, &lt;span style="color: blue"&gt;out int&lt;/span&gt; completionPortThreads);
}&lt;/pre&gt;
&lt;p&gt;不过，这并不意味着CLR线程池中线程被用完之后，还是可以发起异步IO请求。例如，您可以尝试着将这个例子中的WebRequest操作放到for循环后面（确保CLR线程池中线程已经被用完了），这是您会发现BeginGetRequest方法的调用抛出了一个异常，提示您说线程池中没有多余的线程了。从这个角度这样看来，CLR线程池的确还是可能影响异步IO操作的（多谢xiongli大哥指出“这是由具体实现决定的”）——虽然这在普通应用程序中一般不会出现这个问题。
&lt;p&gt;其实在IO线程池方面还可以进行其他一些试验。例如，您可以缩小IO线程池的最大线程数量，然后一下子发起多个异步IO请求，观察一下它们的回调函数执行时刻。这些不如就由您来自行完成了？
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html"&gt;浅谈线程池（上）：线程池的作用及CLR线程池&lt;/a&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html"&gt;浅谈线程池（中）：独立线程池的作用及IO线程池&lt;/a&gt; 
&lt;li&gt;浅谈线程池（下）：相关试验及注意事项&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;注1：.NET 4.0在多线程方面进行了明显的增强，除了Task并行类库之外，也将Parallel Library并入框架之内。此外，.NET 4.0还提供了许多线程安全的并行容器，以及轻量级的CountDownLatch、SemaphoreSlim、SpinWait等常用组件，无论是学习还是使用都是绝佳的范例。 &lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/10/thread-pool-3-lab.html#comments</comments>
      <pubDate>Mon, 19 Oct 2009 16:06:00 GMT</pubDate>
      <lastBuildDate>Mon, 19 Oct 2009 16:06:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>您能看出这个Double Check里的问题吗？（解答）</title>
      <link>http://blog.zhaojie.me/2009/09/double-check-failure-answer.html</link>
      <guid>http://blog.zhaojie.me/2009/09/double-check-failure-answer.html</guid>
      <description>&lt;p&gt;问题请参考：&lt;a href="http://blog.zhaojie.me/2009/09/double-check-failure.html"&gt;您能看出这个Double Check里的问题吗？&lt;/a&gt;&lt;/p&gt; &lt;p&gt;已经很有很多朋友得到了结果，是由于m_categories过早初始化，而导致double check的验证条件被破坏（或者说，满足）。&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private object &lt;/span&gt;m_mutex = &lt;span style="color: blue"&gt;new object&lt;/span&gt;();
&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; m_categories;

&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category &lt;/span&gt;GetCategory(&lt;span style="color: blue"&gt;int &lt;/span&gt;id)
{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        &lt;span style="color: blue"&gt;lock &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_mutex)
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                LoadCategories();
            }
        }
    }

    &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_categories[id];
}

&lt;span style="color: blue"&gt;private void &lt;/span&gt;LoadCategories()
{
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;,&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;();
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.Fill(GetCategoryRoots());
}

&lt;span style="color: blue"&gt;private void &lt;/span&gt;Fill(&lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; categories)
{
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories.Add(cat.CategoryID, cat);
        Fill(cat.Children);
    }
}
&lt;/pre&gt;
&lt;p&gt;假设第一个线程进入了GetCategory方法，它自然可以畅通无阻地执行LoadCategories。只可惜，在LoadCategories方法的第一行就为m_categories设置了一个空字典。如果现在立即有另一个线程访问了GetCategory方法，就会发现m_categories字段不是null，并直接执行this.m_categories[id]这行代码——但此时，第一个线程还没有将这个字典填充完毕！&lt;/p&gt;
&lt;p&gt;因此，这段代码其实是一个有问题的Double Check实现。那么我们该怎么改呢？&lt;/p&gt;
&lt;p&gt;一位匿名朋友提出，可以增加一个标记，用来表示有没有初始化完毕。如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private bool &lt;/span&gt;m_initialized = &lt;span style="color: blue"&gt;false&lt;/span&gt;;

&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category &lt;/span&gt;GetCategory(&lt;span style="color: blue"&gt;int &lt;/span&gt;id)
{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_initialized)
    {
        &lt;span style="color: blue"&gt;lock &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_mutex)
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_initialized)
            {
                LoadCategories();
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_initialized = &lt;span style="color: blue"&gt;true&lt;/span&gt;;
            }
        }
    }

    &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_categories[id];
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这是个非常漂亮的做法，完全没有问题。不过我并没有使用这种修改方式。&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private void &lt;/span&gt;LoadCategories()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;categories = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;,&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;();

    Fill(categories, GetCategoryRoots());

    &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories = categories;
}

&lt;span style="color: blue"&gt;private static void &lt;/span&gt;Fill(&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; container, &lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; categories)
{
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories)
    {
        container.Add(cat.CategoryID, cat);
        Fill(container, cat.Children);
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我稍稍改变了一下Fill方法，它不再直接访问m_categories字段，而是把内容填充至container参数中。而在LoadCategories方法中，我们创建一个字典，但是直到填充完毕后才将其赋给m_categories字段。这样就保证了在m_categories字段不为null的时候，一定已经初始化完毕了。这也是一种可行的办法。我没有使用第一种做法的原因，并不是因为所谓的“节省空间”，而是……一下子就想到了第二种做法。:)&lt;/p&gt;
&lt;p&gt;这里反映了Double Check在使用时的一个准则：&lt;font color="#ff0000"&gt;在满足if条件的时候，一定要确保所有的初始化已经完成了&lt;/font&gt;。或者说，一定要将“满足if条件”的操作放在初始化完毕之后进行。至于是否使用某个标记，倒不是什么大问题。&lt;/p&gt;
&lt;p&gt;如果您使用.NET编写代码，目前已经没有问题了，但是在某些情况下这样的代码还是会出现问题。我认为这也是多线程编程时最麻烦的地方——就是所谓的“Memory Consistency Model”。&lt;/p&gt;
&lt;p&gt;为了性能考虑，编译器在将文本代码转化为机器码，以及CPU在执行机器码时都会对执行进行“重新排序（reorder）”，reorder的作用是为了提升性能。虽然从单线程的角度来看，reorder不会形成问题，但是在多线程的环境中，reorder就会破坏代码的逻辑了。如果没有一个“标准”在进行统一的话，不同的编译器，虚拟机，CPU架构都会有不同的reorder策略。例如微软并行库之父&lt;a href="http://www.bluebytesoftware.com/blog/Default.aspx"&gt;Joe Duffy&lt;/a&gt;在&lt;a href="http://www.bluebytesoftware.com/blog/2009/06/17/ExploringMemoryModels.aspx"&gt;这篇文章&lt;/a&gt;中简单地提到了不同平台（JVM / CLR 2.0）或不同CPU架构（x86 / IA64）下reorder规则的区分。&lt;/p&gt;
&lt;p&gt;而臭名昭著的Double Check的bug便是由于store reordering造成的。在JVM或普通的C、C++中并不保证store reordering不会发生。也就是说，您在代码中看到的两个变量的“设置”顺序，并不代表CPU在执行的时候，也是同样的效果。因此，如果你观察下面的代码：&lt;/p&gt;&lt;pre class="code"&gt;class Foo { 
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) 
            synchronized(this) {
                if (helper == null) 
                    helper = new Helper();
            }
        return helper;
    }
}&lt;/pre&gt;
&lt;p&gt;看上去这是一段再正常不过的实现Double Check的Java代码，但是由于发生了store reordering，可能在Helper构造函数中的操作还没有全部执行完成之前，就设置了helper字段。因此另一个线程就可能会访问到一个没有初始化完整的Helper对象。如果您对这个话题感兴趣，可以参考《&lt;a href="http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html"&gt;The "Double-Checked Locking is Broken" Declaration&lt;/a&gt;》。&lt;/p&gt;
&lt;p&gt;而在CLR 2.0中，只会发生load reordering，而不会出现store reordering。于是.NET中编写的Double Check代码不会出现任何问题。那么CLR是如何保证在不同的CPU平台上出现相同的行为呢？那是因为CLR会根据不同的平台，在合适的情况下插入一些辅助代码（如&lt;a href="http://en.wikipedia.org/wiki/Memory_barrier"&gt;Memory Barrier&lt;/a&gt;），可见CLR为我们的并行编程环境已经形成了一个相对比较方便的平台了——虽然，并行编程还是很困难。&lt;/p&gt;
&lt;p&gt;（似乎关于Memory Model的有些说法不太确切，随时更新，希望了解这些的朋友们也可以提点意见，我晚上回家后再查些资料）&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/double-check-failure-answer.html#comments</comments>
      <pubDate>Wed, 02 Sep 2009 09:16:00 GMT</pubDate>
      <lastBuildDate>Wed, 02 Sep 2009 09:16:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <title>您能看出这个Double Check里的问题吗？</title>
      <link>http://blog.zhaojie.me/2009/09/double-check-failure.html</link>
      <guid>http://blog.zhaojie.me/2009/09/double-check-failure.html</guid>
      <description>&lt;p&gt;昨天在做code review时看到一位同事写了这样的代码。这段代码的目的使用Double Check的做法来保证线程安全的延迟加载。但是我看到这代码之后发现了一个问题，这个问题不是第一次出现。因此，我打算在博客上记录一笔，希望可以给更多人提个醒吧。&lt;/p&gt; &lt;p&gt;假设，我们有这样一个Category类型，记录的是一个树型的分类结构：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public int &lt;/span&gt;CategoryID { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; Children { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;set&lt;/span&gt;; }
}
&lt;/pre&gt;
&lt;p&gt;然后，我们需要一个CategoryLoader，提供一个Get方法从ID获得指定的Category对象：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CategoryLoader
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private object &lt;/span&gt;m_mutex = &lt;span style="color: blue"&gt;new object&lt;/span&gt;();
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; m_categories;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Category &lt;/span&gt;GetCategory(&lt;span style="color: blue"&gt;int &lt;/span&gt;id)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;lock &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_mutex)
            {
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
                {
                    LoadCategories();
                }
            }
        }

        &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_categories[id];
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;LoadCategories()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dictionary&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt;();
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Fill(GetCategoryRoots());
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;Fill(&lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; categories)
    {
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;cat &lt;span style="color: blue"&gt;in &lt;/span&gt;categories)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_categories.Add(cat.CategoryID, cat);
            Fill(cat.Children);
        }
    }

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Category&lt;/span&gt;&amp;gt; GetCategoryRoots() { ... }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;代码的逻辑非常简单：使用一个字典作为容器，在GetCategory方法内部使用Double Check的方式来保证线程安全（即多个线程同时访问同一个对象的GetCategory方法不会出现问题）。如果没有加载，在使用LoadCategories方法构造并填充字典。在LoadCategories方法中会获取所有的“根分类”，并调用Fill方法填充字典。Fill方法会将传入的categories集合添加到字典中，并且递归地将它们的子分类也填充至字典中。&lt;/p&gt;
&lt;p&gt;只可惜，上面的代码有一些问题，导致Double Check没有能够实现我们预期的效果。您能看出这个问题来吗？&lt;/p&gt;
&lt;p&gt;当然，为了演示代码的简单，我省略了很多细节。例如Category的ID缺失或有重复，Category对象不是immutable，Children属性可能会包含null，这可能都会形成问题。不过，我们就暂时不在这方面考究了吧。&lt;/p&gt;
&lt;p&gt;晚上我会公布结果（&lt;a href="http://blog.zhaojie.me/2009/09/double-check-failure-answer.html"&gt;此处&lt;/a&gt;）。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/09/double-check-failure.html#comments</comments>
      <pubDate>Wed, 02 Sep 2009 07:11:00 GMT</pubDate>
      <lastBuildDate>Wed, 02 Sep 2009 07:11:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>适合C# Actor的消息执行方式（6）：协变与逆变</title>
      <link>http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html</link>
      <guid>http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html</guid>
      <description>&lt;p&gt;在&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html"&gt;上一篇文章&lt;/a&gt;中，我们实现了一个简单的爬虫，并指出了这种方式的&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html#drawbacks"&gt;缺陷&lt;/a&gt;。现在，我们就来看一下，如何使用C# 4.0中所引入的“协变和逆变”特性来改进这种消息执行方式，这也是我认为在“普适Actor模型”中最合适的做法。这次，我们动真格的了，我们会一条一条地改进前文提出的缺陷。&lt;/p&gt; &lt;h1&gt;协变与逆变&lt;/h1&gt; &lt;p&gt;在以前的几篇文章中，我们一直挂在嘴边的说法便是消息于Actor类型的“耦合”太高。例如在简单的爬虫实现中，Crawler接受的消息为Crawl(Monitor, string)，它的第一个参数为Monitor类型。但是在实际应用中，这个参数很可能需要是各种类型，唯一的“约束”只是它必须能够接受一个ICrawlResponseHandler类型的消息，这样我们就能把抓取的结果传递给它。至于操作Crawler对象的是Monitor还是Robot，还是我们单元测试时动态创建的Mock对象（这很重要），Crawler一概不会关心。&lt;/p&gt; &lt;p&gt;但就是这个约束，在以前的实现中，我们必须让这个目标继承Actor&amp;lt;ICrawlResponseHandler&amp;gt;，这样它也就无法接受其他类型的消息了。例如Monitor还要负责一些查询操作我们该怎么办呢？幸运的是，在.NET 4.0中，我们只需要让这个目标实现这样一个接口即可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;out &lt;/span&gt;T&amp;gt;
{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Post(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;T&amp;gt; message);
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;瞅到out关键字了没？事实上，还有一个东西您在这里还没有看到，这便是Action委托在.NET 4.0中的签名：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public delegate void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;in &lt;/span&gt;T&amp;gt;(T obj);&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;就在这样一个简单的示例中，协变和逆变所需要的in和out都出现了。这意味着如果有两个类型Parent和Child，其中Child是Parent的子类（或Parent接口的实现），那么实现了IPort&amp;lt;Child&amp;gt;的对象便可以自动赋值给IPort&amp;lt;Parent&amp;gt;类型的参数或引用&lt;sup&gt;1&lt;/sup&gt;。使用代码来说明问题可能会更清楚一些：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parent
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public void &lt;/span&gt;ParentMethod() { };
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Child &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Parent &lt;/span&gt;{ }

&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Child&lt;/span&gt;&amp;gt; childPort = &lt;span style="color: blue"&gt;new&lt;/span&gt; ChildPortType();
    &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Parent&lt;/span&gt;&amp;gt; parentPort = childPort; &lt;span style="color: green"&gt;// 自动转化
    &lt;/span&gt;parentPort.Post(p =&amp;gt; p.ParentMethod()); &lt;span style="color: green"&gt;// 可以接受Action&amp;lt;Parent&amp;gt;类型作为消息
&lt;/span&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这意味着，我们可以把ICrawlRequestHandler和ICrawlResponseHandler类型写成下面的形式：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Crawl(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt; collector, &lt;span style="color: blue"&gt;string &lt;/span&gt;url);
}

&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Succeeded(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&amp;gt; crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: blue"&gt;string &lt;/span&gt;content, &lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; links);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Failed(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&amp;gt; crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如今，Monitor和Crawler便可以写成如下模样：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlRequestHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;&amp;gt; message) { message(&lt;span style="color: blue"&gt;this&lt;/span&gt;); }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;ICrawlRequestHandler Members

    &lt;span style="color: blue"&gt;void &lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&lt;/span&gt;.Crawl(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt; collector, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        &lt;span style="color: blue"&gt;try
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;string &lt;/span&gt;content = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;().DownloadString(url);

            &lt;span style="color: blue"&gt;var &lt;/span&gt;matches = &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Matches(content, &lt;span style="color: #a31515"&gt;&amp;#64;"href=""(http://[^""]+)"""&lt;/span&gt;).Cast&amp;lt;&lt;span style="color: #2b91af"&gt;Match&lt;/span&gt;&amp;gt;();
            &lt;span style="color: blue"&gt;var &lt;/span&gt;links = matches.Select(m =&amp;gt; m.Groups[1].Value).Distinct().ToList();
            collector.Post(m =&amp;gt; m.Succeeded(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, content, links));
        }
        &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
        {
            collector.Post(m =&amp;gt; m.Failed(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, ex));
        }&lt;span style="color: green"&gt;
    &lt;/span&gt;}

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&lt;span style="color: #2b91af"&gt;
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt; message) { message(&lt;span style="color: blue"&gt;this&lt;/span&gt;); }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;ICrawlResponseHandler Members
    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;.Succeeded(...) { ... }
    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;.Failed(...) { ... }
    &lt;span style="color: blue"&gt;#endregion&lt;/span&gt;

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;DispatchCrawlingTasks(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&amp;gt; reusableCrawler)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Count &amp;lt;= 0)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount--;
        }

        &lt;span style="color: blue"&gt;var &lt;/span&gt;url = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Dequeue();
        reusableCrawler.Post(c =&amp;gt; c.Crawl(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url));

        &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Count &amp;gt; 0 &amp;amp;&amp;amp;
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount &amp;lt; &lt;span style="color: blue"&gt;this&lt;/span&gt;.MaxCrawlerCount)
        {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;newUrl = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Dequeue();
            &lt;span style="color: red"&gt;IPort&amp;lt;ICrawlRequestHandler&amp;gt; crawler = new Crawler();
            crawler.Post(c =&amp;gt; c.Crawl(this, newUrl));&lt;/span&gt;

            &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount++;
        }
    }
}&lt;/pre&gt;
&lt;p&gt;Monitor的具体实现和上篇文章区别不大，您可以参考文章末尾给出的完整代码，并配合前文的分析来理解，这里我们只关注被标红的两行代码。&lt;/p&gt;
&lt;p&gt;在第一行中我们创建了一个Crawler类型的对象，并把它赋值给IPort&amp;lt;ICrawlerRequestHandler&amp;gt;类型的变量中。请注意，Crawler对象并没有实现这个接口，它只是实现了IPort&amp;lt;Crawler&amp;gt;及ICrawlerRequestHandler。不过由于IPort&amp;lt;T&amp;gt;支持协变，于是IPort&amp;lt;Crawler&amp;gt;被安全地转换成了IPort&amp;lt;ICrawlerRequestHandler&amp;gt;对象。&lt;/p&gt;
&lt;p&gt;第二行中再次发生了协变：ICrawlRequestHandler.Crawel的第一个参数需要IPort&amp;lt;ICrawlResponseHandler&amp;gt;类型的对象，但是this是Monitor类型的，它并没有实现这个接口。不过，和上面描述的一样，由于IPort&amp;lt;T&amp;gt;支持协变，因此这样的类型转化是安全的，允许的。于是在Crawler类便可以操作一个“抽象”，而不是具体的Monitor类型来办事了。&lt;/p&gt;
&lt;p&gt;神奇不？但就是这么简单。&lt;/p&gt;
&lt;h1&gt;“内部”消息控制&lt;/h1&gt;
&lt;p&gt;在上一篇文章中，我们还提出了Crawler实现的另一个缺点：没有使用异步IO。WebClient本身的DownloadStringAsync方法可以进行异步下载，但是如果在异步完成的后续操作（如分析链接）会在&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html#io-thread-pool"&gt;IO线程池&lt;/a&gt;中运行，这样我们就很难对任务所分配的运算能力进行控制。我们当时提出，可以把后续操作作为消息发送给Crawler本身，也就是进行“内部”消息控制——可惜的是，我们当时无法做到。不过现在，由于Crawler实现的是IPort&amp;lt;Crawler&amp;gt;接口，因此，我们可以把Crawler内部的任何方法作为消息传递给自身，如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlRequestHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;&amp;gt; message) { message(&lt;span style="color: blue"&gt;this&lt;/span&gt;); }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;ICrawlRequestHandler Members

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Crawl(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt; collector, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        &lt;span style="color: #2b91af"&gt;WebClient &lt;/span&gt;client = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;();
        client.DownloadStringCompleted += (sender, e) =&amp;gt;
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(e.Error == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                &lt;span style="color: red"&gt;this.Post(c =&amp;gt; c.Crawled(collector, url, e.Result));&lt;/span&gt;
            }
            &lt;span style="color: blue"&gt;else
            &lt;/span&gt;{
                collector.Post(c =&amp;gt; c.Failed(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, e.Error));
            }
        };

        client.DownloadStringAsync(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Uri&lt;/span&gt;(url));
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;Crawled(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt; collector, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: blue"&gt;string &lt;/span&gt;content)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;matches = &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Matches(content, &lt;span style="color: #a31515"&gt;&amp;#64;"href=""(http://[^""]+)"""&lt;/span&gt;).Cast&amp;lt;&lt;span style="color: #2b91af"&gt;Match&lt;/span&gt;&amp;gt;();
        &lt;span style="color: blue"&gt;var &lt;/span&gt;links = matches.Select(m =&amp;gt; m.Groups[1].Value).Distinct().ToList();

        collector.Post(c =&amp;gt; c.Succeeded(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, content, links));
    }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我们准备了一个private的Crawled方法，如果抓取成功了，我们会把这个方法的调用封装在一条消息中重新发给自身（红色代码）。请注意，这是个私有方法，因此这里完全是在做“内部”消息控制。&lt;/p&gt;
&lt;h1&gt;开启抓取任务&lt;/h1&gt;
&lt;p&gt;在上一篇文章中，我们为Monitor添加了一个Start方法，它的作用是启动URL。我们知道，对单个Actor来说消息的处理是线程安全的，但是这个前提是使用“消息”传递的方式进行通信，如果直接调用Start公有方法，便会破坏这种线程安全特性。不过现在的Monitor已经不受接口的限制，可以自由接受任何它可以执行的消息，因此我们只要对外暴露一个Crawl方法即可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;,
    &lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;,
    &lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Crawl(&lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_allUrls.Contains(url)) &lt;span style="color: blue"&gt;return&lt;/span&gt;;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_allUrls.Add(url);

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount &amp;lt; &lt;span style="color: blue"&gt;this&lt;/span&gt;.MaxCrawlerCount)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount++;
            &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&amp;gt; crawler = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;();
            crawler.Post(c =&amp;gt; c.Crawl(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url));
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Enqueue(url);
        }
    }&lt;span style="color: blue"&gt;
&lt;/span&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是我们便可以向Monitor发送消息，让其抓取特定的URL：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;string&lt;/span&gt;[] urls =
{
    &lt;span style="color: #a31515"&gt;"http://www.cnblogs.com/dudu/"&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;"http://www.cnblogs.com/TerryLee/"&lt;/span&gt;,
    &lt;span style="color: #a31515"&gt;"http://www.cnblogs.com/JeffreyZhao/"
&lt;/span&gt;};

&lt;span style="color: #2b91af"&gt;Random &lt;/span&gt;random = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Random&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.Now.Millisecond);
&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;monitor = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;(10);
&lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;url &lt;span style="color: blue"&gt;in &lt;/span&gt;urls)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;urlToCrawl = url;
    monitor.Post(m =&amp;gt; m.Crawl(urlToCrawl));
    &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(random.Next(1000, 3000));
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;上面的代码会每隔1到3秒发出一个抓取请求。由于我们使用了消息传递的方式进行通信，因此对于Monitor来说，这一切都是线程安全的。我们可以随时随地为Monitor添加抓取任务。&lt;/p&gt;
&lt;h1&gt;接受多种消息（协议）&lt;/h1&gt;
&lt;p&gt;我们再观察一下Monitor的签名：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;可以发现，如今的Monitor已经和它实现的协议没有一对一的关系了。也就是说，它可以添加任意功能，可以接受任意类型的消息，我们只要让它实现另一个接口即可。于是乎，我们再要一个“查询”功能&lt;sup&gt;2&lt;/sup&gt;：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;GetCrawledCount(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt; requester);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;GetContent(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt; requester, &lt;span style="color: blue"&gt;string &lt;/span&gt;url);
}

&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;ReplyCrawledCount(&lt;span style="color: blue"&gt;int &lt;/span&gt;count);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;ReplyContent(&lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: blue"&gt;string &lt;/span&gt;content);
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;为了让Monior支持查询，我们还需要为它添加这样的代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;&amp;gt;,
    &lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;,
    &lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;IStatisticRequestHandelr Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr&lt;/span&gt;.GetCrawledCount(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt; requester)
    {
        requester.Post(r =&amp;gt; r.ReplyCrawledCount(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_urlContent.Count));
    }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr&lt;/span&gt;.GetContent(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt; requester, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        &lt;span style="color: blue"&gt;string &lt;/span&gt;content;
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_urlContent.TryGetValue(url, &lt;span style="color: blue"&gt;out &lt;/span&gt;content))
        {
            content = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
        }

        requester.Post(r =&amp;gt; r.ReplyContent(url, content));
    }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;最后，我们来尝试着使用这个“查询”功能。首先，我们编写一个测试用的TestStatisticPort类：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TestStatisticPort &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IStatisticResponseHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr&lt;/span&gt;&amp;gt; m_statisticPort;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;TestStatisticPort(&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticRequestHandelr&lt;/span&gt;&amp;gt; statisticPort)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_statisticPort = statisticPort;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Start()
    {
        &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.ReadLine();
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_statisticPort.Post(s =&amp;gt; s.GetCrawledCount(&lt;span style="color: blue"&gt;this&lt;/span&gt;));
        }
    }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;IPort&amp;lt;IStatisticResponseHandler&amp;gt; Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPort&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt;.Post(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;&amp;gt; message)
    {
        message(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;#endregion

    #region &lt;/span&gt;IStatisticResponseHandler Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;.ReplyCrawledCount(&lt;span style="color: blue"&gt;int &lt;/span&gt;count)
    {
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Crawled: {0}"&lt;/span&gt;, count);
    }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IStatisticResponseHandler&lt;/span&gt;.ReplyContent(&lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: blue"&gt;string &lt;/span&gt;content) { ... }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;当调用Start方法时，控制台将会等待用户敲击回车键。当按下回车键时，TestStatisticPort将会向Monitor发送一个IStatisticRequestHandler.GetCrawledCount消息。Monitor回复之后，屏幕上便会显示当前已经抓取成功的URL数目。例如，我们可以编写如下的测试代码：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;monitor = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;(5);
    monitor.Post(m =&amp;gt; m.Crawl(&lt;span style="color: #a31515"&gt;"http://www.cnblogs.com/"&lt;/span&gt;));

    &lt;span style="color: #2b91af"&gt;TestStatisticPort &lt;/span&gt;testPort = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TestStatisticPort&lt;/span&gt;(monitor);
    testPort.Start();
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;随意敲击几下回车，结果如下：&lt;/p&gt;&lt;a title="Crawl Statistic" href="http://img.zhaojie.me/blog/168980/o_crawl-statistic.png" target="_blank"&gt;&lt;img alt="Crawl Statistic" src="http://img.zhaojie.me/blog/168980/o_crawl-statistic.png" width="450"&gt;&lt;/a&gt; 
&lt;h1&gt;总结&lt;/h1&gt;
&lt;p&gt;如今的做法，兼顾了强类型检查，并使用C# 4.0中的协变和逆变特性，把上一篇文章中提出的问题解决了，不知您是否理解了这些内容？只可惜，我们在C# 3.0中还没有协变和逆变。因此，我们还必须思考一个适合C# 3.0的做法。&lt;/p&gt;
&lt;p&gt;顺便一提，由于F#不支持协变和逆变，因此本文的做法无法在F#中使用。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;适合C# Actor的消息执行方式（1）：Erlang中的模式匹配&lt;/a&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html"&gt;适合C# Actor的消息执行方式（2）：C# Actor的尴尬&lt;/a&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html"&gt;适合C# Actor的消息执行方式（3）：中看不中用的解决方案&lt;/a&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html"&gt;适合C# Actor的消息执行方式（4）：阶段性总结&lt;/a&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html"&gt;适合C# Actor的消息执行方式（5）：一个简单的网络爬虫&lt;/a&gt; 
&lt;li&gt;适合C# Actor的消息执行方式（6）：协变与逆变&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注1：&lt;/strong&gt;关于协变和逆变特性，我认为脑袋兄的&lt;a href="http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html"&gt;这篇文章&lt;/a&gt;讲的非常清楚——您看得头晕了？是的，刚开始了解协变和逆变，以及它们之间的嵌套规则时我也头晕，但是您在掌握之后就会发现，这的确是一个非常有用的特性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注2：&lt;/strong&gt;不知您是否发现，与之前internal的Crawl相关接口不同，Statistic相关接口是public的。我们在使用接口作为消息时，也可以通过这种办法来控制哪些消息是可以对外暴露的。这也算是一种额外的收获吧。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;本文完整代码：&lt;a href="http://gist.github.com/160043"&gt;http://gist.github.com/160043&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html#comments</comments>
      <pubDate>Mon, 03 Aug 2009 01:19:00 GMT</pubDate>
      <lastBuildDate>Mon, 03 Aug 2009 01:19:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>适合C# Actor的消息执行方式（5）：一个简单的网络爬虫</title>
      <link>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html</link>
      <guid>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html</guid>
      <description>&lt;p&gt;之前的几篇文章大都在摆一些“小道理”，有经验的朋友容易想象出来其中的含义，不过对于那些还不了解Actor模型的朋友来说，这些内容似乎有些太过了。此外，乒乓测试虽然经典，但是不太容易说明问题。因此，今天我们就来看一个简单的有些简陋的网络爬虫，对于Actor模型的使用来说，它至少比乒乓测试能够说明问题。对了，我们先来使用那“中看不中用”的消息执行方式。&lt;/p&gt; &lt;h1&gt;功能简介&lt;/h1&gt; &lt;p&gt;这个网络爬虫的功能还是用于演示，先来列举出它的实现目标吧：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;给出一个初始链接，然后抓取它的HTML并分析出所有html链接，然后继续爬，不断爬，直到爬完所有链接为止。  &lt;li&gt;多线程运行，我们可以指定由多少个爬虫同时工作。  &lt;li&gt;多个爬虫组成一个“工作单元”，程序中可以同时出现多个工作单元，工作单元之间互相独立。  &lt;li&gt;能简化的地方便简化，如一切不涉及任何永久性存储（也就是说，只使用内存），没有太复杂的容错机制。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;的确很简单吧？那么，现在您不妨先在脑海中想象一下，在不用Actor模型的时候您会怎么实现这个功能。然后，我们就要动手使用ActorLite这个小类库了。&lt;/p&gt; &lt;h1&gt;协议制定&lt;/h1&gt; &lt;p&gt;正如我们不断强调的那样，在Actor模型中唯一的通信方式便是互相发送消息。于是使用Actor模型的第一步往往便是设计Actor类型，以及它们之间传递的消息。在这个简单的场景中，我们会定义两种Actor类型。一是Monitor，二是Crawler。一个Monitor便代表一个“工作单元”，它管理了多个爬虫，即Crawler。&lt;/p&gt; &lt;p&gt;Monitor将负责在合适的时候创建Crawler，并向其发送一个消息，让其开始工作。在我们的系统中，我们使用ICrawlRequestHandler接口来表示这个消息：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Crawl(&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;monitor, &lt;span style="color: blue"&gt;string &lt;/span&gt;url);
}
&lt;/pre&gt;
&lt;p&gt;在接受到上面的Crawl消息后，Crawler将去抓取指定的url对象，并将结果发还给Monitor。在这里我们要求报告Cralwer向Monitor报告“成功”和“失败”两种消息&lt;sup&gt;1&lt;/sup&gt;：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Succeeded(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; links);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Failed(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我们使用“接口”这种方式定义了“消息组”，把Succeeded和Failed两种关系密切的消息绑定在一起。如果抓取成功，则Crawler会从抓取内容中获得额外的链接，并发还给Monitor——失败的时候自然就发还一个异常对象。此外，无论是成功还是失败，我们都会把Crawler对象交给Monitor，Monitor会安排给Crawler新的抓取任务。&lt;/p&gt;
&lt;p&gt;因此，Monitor和Cralwer类的定义大约应该是这样的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlResponseHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt; message)
    {
        message(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;ICrawlResponseHandler Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;.Succeeded(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; links)
    {
        ...
    }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;.Failed(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
    {
        ...
    }

    &lt;span style="color: blue"&gt;#endregion&lt;/span&gt;
}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlRequestHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;&amp;gt; message)
    {
        message(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;ICrawlRequestHandler Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;.Crawl(&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;monitor, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        ...
    }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;h1&gt;Crawler实现&lt;/h1&gt;
&lt;p&gt;我们先从简单的Crawler类的实现开始。Crawler类只需要实现ICrawlRequestHandler接口的Crawl方法即可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;.Crawl(&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;monitor, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
{
    &lt;span style="color: blue"&gt;try
    &lt;/span&gt;{
        &lt;span style="color: blue"&gt;string &lt;/span&gt;content = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;().DownloadString(url);

        &lt;span style="color: blue"&gt;var &lt;/span&gt;matches = &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Matches(content, &lt;span style="color: #a31515"&gt;&amp;#64;"href=""(http://[^""]+)"""&lt;/span&gt;).Cast&amp;lt;&lt;span style="color: #2b91af"&gt;Match&lt;/span&gt;&amp;gt;();
        &lt;span style="color: blue"&gt;var &lt;/span&gt;links = matches.Select(m =&amp;gt; m.Groups[1].Value).Distinct().ToList();
        monitor.Post(m =&amp;gt; m.Succeeded(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, links));
    }
    &lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
    {
        monitor.Post(m =&amp;gt; m.Failed(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, ex));
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;没错，使用WebClient下载页面内容只需要一行代码就可以了。然后便是使用正则表达式提取出页面上所有的链接。很显然这里是有问题的，因为我们我只分析出以“http://”开头的地址，但是无视其他的“相对地址”——不过作为一个小实验来说已经足够说明问题了。最后自然是使用Post方法将结果发还给Monitor。在抛出异常的情况下，这几行代码的逻辑也非常自然。&lt;/p&gt;
&lt;h1&gt;Monitor实现&lt;/h1&gt;
&lt;p&gt;Monitor相对来说便略显复杂了一些。我们知道，Monitor要负责控制Crawler的数量，那么必然需要负责维护一些必要的字段：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HashSet&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; m_allUrls; &lt;span style="color: green"&gt;// 所有待爬或爬过的url
&lt;/span&gt;&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Queue&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; m_readyToCrawl; &lt;span style="color: green"&gt;// 待爬的url

&lt;/span&gt;&lt;span style="color: blue"&gt;public int &lt;/span&gt;MaxCrawlerCount { &lt;span style="color: blue"&gt;private set&lt;/span&gt;; &lt;span style="color: blue"&gt;get&lt;/span&gt;; } &lt;span style="color: green"&gt;// 最大爬虫数目
&lt;/span&gt;&lt;span style="color: blue"&gt;public int &lt;/span&gt;WorkingCrawlerCount { &lt;span style="color: blue"&gt;private set&lt;/span&gt;; &lt;span style="color: blue"&gt;get&lt;/span&gt;; } &lt;span style="color: green"&gt;// 正在工作的爬虫数目

&lt;/span&gt;&lt;span style="color: blue"&gt;public &lt;/span&gt;Monitor(&lt;span style="color: blue"&gt;int &lt;/span&gt;maxCrawlerCount)
{
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_allUrls = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HashSet&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;();
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Queue&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;();
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.MaxCrawlerCount = maxCrawlerCount;
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount = 0;
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;Monitor要处理的自然是ICrawlResponseHandler中的Succeeded或Failed方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;.Succeeded(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; links)
{
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"{0} crawled, {1} link(s)."&lt;/span&gt;, url, links.Count);

    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;newUrl &lt;span style="color: blue"&gt;in &lt;/span&gt;links)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_allUrls.Contains(newUrl))
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_allUrls.Add(newUrl);
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Enqueue(newUrl);
        }
    }

    &lt;span style="color: blue"&gt;this&lt;/span&gt;.DispatchCrawlingTasks(crawler);
}

&lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;.Failed(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;crawler, &lt;span style="color: blue"&gt;string &lt;/span&gt;url, &lt;span style="color: #2b91af"&gt;Exception &lt;/span&gt;ex)
{
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"{0} error occurred: {1}."&lt;/span&gt;, url, ex.Message);
    &lt;span style="color: blue"&gt;this&lt;/span&gt;.DispatchCrawlingTasks(crawler);
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;在抓取成功时，Monitor将遍历links列表中的所有地址，如果发现新的url，则加入相关集合中。在抓取失败的情况下，我们也只是简单的继续下去而已。而“继续”则是由DispatchCrawlingTasks方法实现的，我们需要传入一个“可复用”的Crawler对象：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private void &lt;/span&gt;DispatchCrawlingTasks(&lt;span style="color: #2b91af"&gt;Crawler &lt;/span&gt;reusableCrawler)
{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Count &amp;lt;= 0)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount--;
        &lt;span style="color: blue"&gt;return&lt;/span&gt;;
    }

    &lt;span style="color: blue"&gt;var &lt;/span&gt;url = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Dequeue();
    reusableCrawler.Post(c =&amp;gt; c.Crawl(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url));

    &lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Count &amp;gt; 0 &amp;amp;&amp;amp;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount &amp;lt; &lt;span style="color: blue"&gt;this&lt;/span&gt;.MaxCrawlerCount)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;newUrl = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_readyToCrawl.Dequeue();
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;().Post(c =&amp;gt; c.Crawl(&lt;span style="color: blue"&gt;this&lt;/span&gt;, newUrl));

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount++;
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如果已经没有需要抓取的内容了，则直接抛弃Crawler对象即可，否则则分派一个新任务。接着便不断创建新的爬虫，分配新的抓取任务，直到爬虫数额用满，或者没有需要抓取的内容位置。&lt;/p&gt;
&lt;h1&gt;使用&lt;/h1&gt;
&lt;p&gt;我们使用区区几十行代码遍实现了一个简单的多线程爬虫，其中一个关键便是使用了Actor模型。使用Actor模型，对象之间通过消息传递进行交互。而且对于单个Actor对象来说，消息的执行完全是线程安全的。因此，我们只要作用最直接的逻辑便可以完成整个实现，从而回避了内存共享的并行模式中所使用的互斥体、锁等各类组件。&lt;/p&gt;
&lt;p&gt;不过有没有发现，我们没有一个入口可以“开启”一个抓取任务啊，Monitor类中还缺少了点什么。好吧，那么我们补上一个Start方法：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ICrawlResponseHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;ICrawlResponseHandler
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Start(&lt;span style="color: blue"&gt;string &lt;/span&gt;url)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_allUrls.Add(url);
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.WorkingCrawlerCount++;
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Crawler&lt;/span&gt;().Post(c =&amp;gt; c.Crawl(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url));
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;于是，我们便可以这样打开一个或多个抓取任务：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Program
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
    {
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;(5).Start(&lt;span style="color: #a31515"&gt;"http://www.cnblogs.com/"&lt;/span&gt;);
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Monitor&lt;/span&gt;(10).Start(&lt;span style="color: #a31515"&gt;"http://www.csdn.net/"&lt;/span&gt;);

        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.ReadLine();
    }
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这里我们新建两个工作单元，也就是启动了两个抓取任务。一是使用5个爬虫抓取cnblogs.com，二是使用10个爬虫抓取csdn.net。&lt;/p&gt;
&lt;h1 id="drawbacks"&gt;缺陷&lt;/h1&gt;
&lt;p&gt;这里的缺陷是什么？其实很明显，您发现了吗？&lt;/p&gt;
&lt;p&gt;使用Actor模型可以保证消息执行的线程安全，不过很明显Start方法并非如此，我们只能用它来“开启”一个抓取任务。但是如果我们想再次“手动”提交一个需要抓取的URL怎么办？所以理想的方法，其实也应该是向Monitor发送一个消息来启动第一个URL抓取任务。需要补充，则发送多个URL即可。可是，这个消息定义在什么地方才合适呢？我们的Monitor类已经实现了Actor&amp;lt;Action&amp;lt;ICrawlResponseHandler&amp;gt;&amp;gt;，已经没有办法接受另一个接口作为消息了，不是吗？&lt;/p&gt;
&lt;p&gt;这就是一个致命的限制：一个Actor虽然可以实现多个接口，但只能接受其中一个作为消息。同样的，如果我们要为Monitor提供其他功能，例如“查询”某个URL的抓取状态，也因为同样的原因而无法实现。还有，便是在前几篇文章中谈到的问题了。Crawler和Monitor直接耦合，我们向Crawler发送的消息只能携带一个Monitor对象。&lt;/p&gt;
&lt;p&gt;最后，便是一个略显特别的问题了。我们这里使用WebClient的DownloadString方法来获取网页的内容，但是这是个同步IO操作，理想的做法中我们应该使用异步的方法。所以，我们可以这么写：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ICrawlRequestHandler&lt;/span&gt;.Crawl(&lt;span style="color: #2b91af"&gt;Monitor &lt;/span&gt;monitor, &lt;span style="color: blue"&gt;string &lt;/span&gt;url)
{
    &lt;span style="color: #2b91af"&gt;WebClient &lt;/span&gt;webClient = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;();
    webClient.DownloadStringCompleted += (sender, e) =&amp;gt;
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(e.Error == &lt;span style="color: blue"&gt;null&lt;/span&gt;)
        {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;matches = &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Matches(e.Result, &lt;span style="color: #a31515"&gt;&amp;#64;"href=""(http://[^""]+)"""&lt;/span&gt;).Cast&amp;lt;&lt;span style="color: #2b91af"&gt;Match&lt;/span&gt;&amp;gt;();
            &lt;span style="color: blue"&gt;var &lt;/span&gt;links = matches.Select(m =&amp;gt; m.Groups[1].Value).Distinct().ToList();
            monitor.Post(m =&amp;gt; m.Succeeded(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, links));
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            monitor.Post(m =&amp;gt; m.Failed(&lt;span style="color: blue"&gt;this&lt;/span&gt;, url, e.Error));
        }
    };
    webClient.DownloadStringAsync(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Uri&lt;/span&gt;(url));
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;如果您还记得老赵在最近一篇文章中&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html"&gt;关于IO线程池的讨论&lt;/a&gt;，就可以了解到DownloadStringCompleted事件的处理方法会在统一的IO线程池中运行，这样我们无法控制其运算能力。因此，我们应该在回调函数中向Crawler自己发送一条消息表示抓取完毕……呃，但是我们现在做不到啊。&lt;/p&gt;
&lt;p&gt;嗯，下次再说吧。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;适合C# Actor的消息执行方式（1）：Erlang中的模式匹配&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html"&gt;适合C# Actor的消息执行方式（2）：C# Actor的尴尬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html"&gt;适合C# Actor的消息执行方式（3）：中看不中用的解决方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html"&gt;适合C# Actor的消息执行方式（4）：阶段性总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;适合C# Actor的消息执行方式（5）：一个简单的网络爬虫&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html"&gt;适合C# Actor的消息执行方式（6）：协变与逆变&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;本文完整代码：&lt;a href="http://gist.github.com/154815" title="code"&gt;http://gist.github.com/154815&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html#comments</comments>
      <pubDate>Mon, 27 Jul 2009 01:13:00 GMT</pubDate>
      <lastBuildDate>Mon, 27 Jul 2009 01:13:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <title>浅谈线程池（中）：独立线程池的作用及IO线程池</title>
      <link>http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html</link>
      <guid>http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html</guid>
      <description>&lt;p&gt;在&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html"&gt;上一篇文章&lt;/a&gt;中，我们简单讨论了线程池的作用，以及CLR线程池的一些特性。不过关于线程池的基本概念还没有结束，这次我们再来补充一些必要的信息，有助于我们在程序中选择合适的使用方式。&lt;/p&gt; &lt;h1&gt;独立线程池&lt;/h1&gt; &lt;p&gt;上次我们讨论到，在一个.NET应用程序中会有一个CLR线程池，可以使用ThreadPool类中的静态方法来使用这个线程池。我们只要使用QueueUserWorkItem方法向线程池中添加任务，线程池就会负责在合适的时候执行它们。我们还讨论了CLR线程池的一些高级特性，例如对线程的最大和最小数量作限制，对线程创建时间作限制以避免突发的大量任务消耗太多资源等等。&lt;/p&gt; &lt;p&gt;那么.NET提供的线程池又有什么缺点呢？有些朋友说，一个重要的缺点就是功能太简单，例如只有一个队列，没法做到对多个队列作轮询，无法取消任务，无法设定任务优先级，无法限制任务执行速度等等。不过其实这些简单的功能，倒都可以通过在CLR线程池上增加一层（或者说，通过封装CLR线程池）来实现。例如，您可以让放入CLR线程池中的任务，在执行时从几个自定义任务队列中挑选一个运行，这样便达到了对多个队列作轮询的效果。因此，在我看来，CLR线程池的主要缺点并不在此。&lt;/p&gt; &lt;p&gt;我认为，CLR线程池的主要问题在于“大一统”，也就是说，整个进程内部几乎所有的任务都会依赖这个线程池。如前篇文章所说的那样，如Timer和WaitForSingleObject，还有委托的异步调用，.NET框架中的许多功能都依赖这个线程池。这个做法是合适的，但是由于开发人员对于统一的线程池无法做到精确控制，因此在一些特别的需要就无法满足了。举个最常见例子：控制运算能力。什么是运算能力？那么还是从线程讲起吧&lt;sup&gt;1&lt;/sup&gt;。&lt;/p&gt; &lt;p&gt;我们在一个程序中创建一个线程，安排给它一个任务，便交由操作系统来调度执行。操作系统会管理系统中所有的线程，并且使用一定的方式进行调度。什么是“调度”？调度便是控制线程的状态：执行，等待等等。我们都知道，从理论上来说有多少个处理单元（如2 * 2 CPU的机器便有4个处理单元），就表示操作系统可以同时做几件事情。但是线程的数量会远远超过处理单元的数量，因此操作系统为了保证每个线程都被执行，就必须等一个线程在某个处理器上执行到某个情况的时候，“换”一个新的线程来执行，这便是所谓的“上下文切换（context switch）”。至于造成上下文切换的原因也有多种，可能是某个线程的逻辑决定的，如遇上锁，或主动进入休眠状态（调用Thread.Sleep方法），但更有可能是操作系统发现这个线程“超时”了。在操作系统中会定义一个“时间片（timeslice）”&lt;sup&gt;2&lt;/sup&gt;，当发现一个线程执行时间超过这个时间，便会把它撤下，换上另外一个。这样看起来，多个线程——也就是多个任务在同时运行了。&lt;/p&gt; &lt;p&gt;值得一提的是，对于Windows操作系统来说，它的调度单元是线程，这和线程究竟属于哪个进程并没有关系。举个例子，如果系统中只有两个进程，进程A有5个线程，而进程B有10个线程。在排除其他因素的情况下，进程B占有运算单元的时间便是进程A的两倍。当然，实际情况自然不会那么简单。例如不同进程会有不同的优先级，线程相对于自己所属的进程还会有个优先级；如果一个线程在许久没有执行的时候，或者这个线程刚从“锁”的等待中恢复，操作系统还会对这个线程的优先级作临时的提升——这一切都是牵涉到程序的运行状态，性能等情况的因素，有机会我们在做展开。&lt;/p&gt; &lt;p&gt;现在您意识到线程数量意味着什么了没？没错，就是我们刚才提到的“运算能力”。很多时候我们可以简单的认为，在同样的环境下，一个任务使用的线程数量越多，它所获得的运算能力就比另一个线程数量较少的任务要来得多。运算能力自然就涉及到任务执行的快慢。您可以设想一下，有一个生产任务，和一个消费任务，它们使用一个队列做临时存储。在理想情况下，生产和消费的速度应该保持相同，这样可以带来最好的吞吐量。如果生产任务执行较快，则队列中便会产生堆积，反之消费任务就会不断等待，吞吐量也会下降。因此，在实现的时候，我们往往会为生产任务和消费任务分别指派独立的线程池，并且通过增加或减少线程池内线程数量来条件运算能力，使生产和消费的步调达到平衡。&lt;/p&gt; &lt;p&gt;使用独立的线程池来控制运算能力的做法很常见，一个典型的案例便是&lt;a href="http://www.eecs.harvard.edu/~mdw/proj/seda/"&gt;SEDA架构&lt;/a&gt;：整个架构由多个Stage连接而成，每个Stage均由一个队列和一个独立的线程池组成，调节器会根据队列中任务的数量来调节线程池内的线程数量，最终使应用程序获得优异的并发能力。&lt;/p&gt; &lt;p&gt;在Windows操作系统中，&lt;a href="http://msdn.microsoft.com/en-us/library/ms686756(VS.85).aspx"&gt;Server 2003及之前版本的API&lt;/a&gt;也只提供了进程内部单一的线程池，不过在&lt;a href="http://msdn.microsoft.com/en-us/library/ms686766(VS.85).aspx"&gt;Vista及Server 2008的API&lt;/a&gt;中，除了改进线程池的性能之外，还提供了在同一进程内创建多个线程池的接口。很可惜，.NET直到如今的4.0版本，依旧没有提供构建独立线程池的功能。构造一个优秀的线程池是一件相当困难的事情，幸运的是，如果我们需要这方面的功能，可以借助著名的&lt;a href="http://www.codeproject.com/KB/threads/smartthreadpool.aspx"&gt;SmartThreadPool&lt;/a&gt;，经过那么多年的考验，相信它已经足够成熟了。如果需要，我们还可以对它做一定修改——毕竟在不同情况下，我们对线程池的要求也不完全相同。&lt;/p&gt; &lt;h1 id="io-thread-pool"&gt;IO线程池&lt;/h1&gt; &lt;p&gt;IO线程池便是为异步IO服务的线程池。&lt;/p&gt; &lt;p&gt;访问IO最简单的方式（如读取一个文件）便是阻塞的，代码会等待IO操作成功（或失败）之后才继续执行下去，一切都是顺序的。但是，阻塞式IO有很多缺点，例如让UI停止响应，造成上下文切换，CPU中的缓存也可能被清除甚至内存被交换到磁盘中去，这些都是明显影响性能的做法。此外，每个IO都占用一个线程，容易导致系统中线程数量很多，最终限制了应用程序的伸缩性。因此，我们会使用“异步IO”这种做法。&lt;/p&gt; &lt;p&gt;在使用异步IO时，访问IO的线程不会被阻塞，逻辑将会继续下去。操作系统会负责把结果通过某种方法通知我们，一般说来，这种方式是“回调函数”。异步IO在执行过程中是不占用应用程序的线程的，因此我们可以用少量的线程发起大量的IO，所以应用程序的响应能力也可以有所提高。此外，同时发起大量IO操作在某些时候会有额外的性能优势，例如磁盘和网络可以同时工作而不互相冲突，磁盘还可以根据磁头的位置来访问就近的数据，而不是根据请求的顺序进行数据读取，这样可以有效减少磁头的移动距离。&lt;/p&gt; &lt;p&gt;Windows操作系统中有多种异步IO方式，但是性能最高，伸缩性最好的方式莫过于传说中的“IO完成端口（I/O Completion Port，IOCP）”了，这也是.NET中封装的唯一异步IO方式。大约一年半前，老赵写过一篇文章《&lt;a href="http://blog.zhaojie.me/2008/02/use-async-operation-properly.html"&gt;正确使用异步操作&lt;/a&gt;》，其中除了描述计算密集型和IO密集型操作的区别和效果之外，还简单地讲述了IOCP与CLR交互的方式，摘录如下：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;当我们希望进行一个异步的IO-Bound Operation时，CLR会（通过Windows API）发出一个IRP（I/O Request Packet）。当设备准备妥当，就会找出一个它“最想处理”的IRP（例如一个读取离当前磁头最近的数据的请求）并进行处理，处理完毕后设备将会（通过Windows）交还一个表示工作完成的IRP。CLR会为每个进程创建一个IOCP（I/O Completion Port）并和Windows操作系统一起维护。IOCP中一旦被放入表示完成的IRP之后（通过内部的ThreadPool.BindHandle完成），CLR就会尽快分配一个可用的线程用于继续接下去的任务。&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;不过事实上，使用Windows API编写IOCP非常复杂。而在.NET中，由于需要迎合标准的APM（异步编程模型），在使用方便的同时也放弃一定的控制能力。因此，在一些真正需要高吞吐量的时候（如编写服务器），不少开发人员还是会选择直接使用Native Code编写相关代码。不过在绝大部分的情况下，.NET中利用IOCP的异步IO操作已经足以获得非常优秀的性能了。使用APM方式在.NET中使用异步IO非常简单，如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{&lt;span style="color: green"&gt;
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebRequest &lt;/span&gt;request = &lt;span style="color: #2b91af"&gt;HttpWebRequest&lt;/span&gt;.Create(&lt;span style="color: #a31515"&gt;"http://www.cnblogs.com"&lt;/span&gt;);
    request.BeginGetResponse(HandleAsyncCallback, request);
}

&lt;span style="color: blue"&gt;static void &lt;/span&gt;HandleAsyncCallback(&lt;span style="color: #2b91af"&gt;IAsyncResult &lt;/span&gt;ar)
{
    &lt;span style="color: #2b91af"&gt;WebRequest &lt;/span&gt;request = (&lt;span style="color: #2b91af"&gt;WebRequest&lt;/span&gt;)ar.AsyncState;
    &lt;span style="color: #2b91af"&gt;WebResponse &lt;/span&gt;response = request.EndGetResponse(ar);
    &lt;span style="color: green"&gt;// more operations...
&lt;/span&gt;}&lt;/pre&gt;
&lt;p&gt;BeginGetResponse将发起一个利用IOCP的异步IO操作，并在结束时调用HandleAsyncCallback回调函数。那么，这个回调函数是由哪里的线程执行的呢？没错，就是传说中“IO线程池”的线程。.NET在一个进程中准备了两个线程池，除了上篇文章中所提到的CLR线程池之外，它还为异步IO操作的回调准备了一个IO线程池。IO线程池的特性与CLR线程池类似，也会动态地创建和销毁线程，并且也拥有最大值和最小值（可以参考上一篇文章列举出的API）。&lt;/p&gt;
&lt;p&gt;只可惜，IO线程池也仅仅是那“一整个”线程池，CLR线程池的缺点IO线程池也一应俱全。例如，在使用异步IO方式读取了一段文本之后，下一步操作往往是对其进行分析，这就进入了计算密集型操作了。但对于计算密集型操作来说，如果使用整个IO线程池来执行，我们无法有效的控制某项任务的运算能力。因此在有些时候，我们在回调函数内部会把计算任务再次交还给独立的线程池。这么做从理论上看会增大线程调度的开销，不过实际情况还得看具体的评测数据。如果它真的成为影响性能的关键因素之一，我们就可能需要使用Native Code来调用IOCP相关API，将回调任务直接交给独立的线程池去执行了。&lt;/p&gt;
&lt;p&gt;我们也可以使用代码来操作IO线程池，例如下面这个接口便是向IO线程池递交一个任务：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;
{
    &lt;span style="color: blue"&gt;public static bool &lt;/span&gt;UnsafeQueueNativeOverlapped(&lt;span style="color: #2b91af"&gt;NativeOverlapped&lt;/span&gt;* overlapped);
}&lt;/pre&gt;
&lt;p&gt;NativeOverlapped包含了一个IOCompletionCallback回调函数及一个缓冲对象，可以通过Overlapped对象创建。Overlapped会包含一个被固定的空间，这里“固定”的含义表示不会因为GC而导致地址改变，甚至不会被置换到硬盘上的Swap空间去。这么做的目的是迎合IOCP的要求，但是很明显它也会降低程序性能。因此，我们在实际编程中几乎不会使用这个方法&lt;sup&gt;3&lt;/sup&gt;。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html"&gt;浅谈线程池（上）：线程池的作用及CLR线程池&lt;/a&gt; 
&lt;li&gt;浅谈线程池（中）：独立线程池的作用及IO线程池 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/thread-pool-3-lab.html"&gt;浅谈线程池（下）：相关试验及注意事项&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注1：&lt;/strong&gt;如果没有加以说明，我们这里谈论的对象默认为XP及以上版本的Window操作系统。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注2：&lt;/strong&gt;timeslice又被称为quantum，不同操作系统中定义的这个值并不相同。在Windows客户端操作系统（XP，Vista）中时间片默认为2个clock interval，在服务器操作系统（2003，2008）中默认为12个clock interval（在主流系统上，1个clock interval大约10到15毫秒）。服务器操作系统使用较长的时间片，是因为一般服务器上运行的程序比客户端要少很多，且更注重性能和吞吐量，而客户端系统更注重响应能力——而且，如果您真需要的话，时间片的长度也是可以调整的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注3：&lt;/strong&gt;不过，如果程序中多次复用单个NativeOverlapped对象的话，这个方法的性能会略微好于QueueUserWorkItem，据说WCF中便使用了这种方式——微软内部总有那么些技巧是我们不知如何使用的，例如老赵记得之前查看ASP.NET AJAX源代码的时候，在MSDN中不小心发现一个接口描述大意是“预留方法，请不要在外部使用”。对此，我们又能有什么办法呢？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html#comments</comments>
      <pubDate>Fri, 24 Jul 2009 01:21:00 GMT</pubDate>
      <lastBuildDate>Fri, 24 Jul 2009 01:21:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/asp-net/">ASP.NET</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <title>浅谈线程池（上）：线程池的作用及CLR线程池</title>
      <link>http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html</link>
      <guid>http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html</guid>
      <description>&lt;p&gt;线程池是一个重要的概念。不过我发现，关于这个话题的讨论似乎还缺少了点什么。作为资料的补充，以及今后文章所需要的引用，我在这里再完整而又简单地谈一下有关线程池，还有.NET中各种线程池的基础。更详细的内容就不多作展开了，有机会我们再详细讨论这方面的细节。这次，还是一个“概述”性质的，希望可以说明白这方面问题的一些概念。&lt;/p&gt; &lt;h1&gt;线程池的作用&lt;/h1&gt; &lt;p&gt;其实“线程池”就是用来存放“线程”的对象池。&lt;/p&gt; &lt;p&gt;在程序中，如果某个创建某种对象所需要的代价太高，同时这个对象又可以反复使用，那么我们往往就会准备一个容器，用来保存一批这样的对象。于是乎，我们想要用这种对象时，就不需要每次去创建一个，而直接从容器中取出一个现成的对象就可以了。由于节省了创建对象的开销，程序性能自然就上升了。这个容器就是“池”。很容易理解的是，因为有了对象池，因此在用完对象之后必须有一个“归还”的动作，这样便可以把对象放回池中，下次需要的时候就可以再次拿出来使用了。&lt;/p&gt; &lt;p&gt;例如，我们在使用ADO.NET连接SQL Server时，.NET框架就会自动帮我们维护一个连接池，这就是因为重新创建一个连接的代价相对比较高昂，“复用”就显得比较划算了。不过有些朋友可能会说，我们明明是每次都创建一个SqlConnection对象，哪里有“复用”啊？这是因为.NET框架中把“连接池”做透明了，对于程序员完全隐藏了这个概念。每次我们虽然创建的是新的SqlConnection对象，但是这个对象内部占用的“数据库连接”还是会复用的。为什么总是强调用完SqlConnection对象后要及时“关闭”（Dispose或Close）呢？其实这里并没有断开数据库连接，只是把这个连接放回了连接池。等到下次创建新的SqlConnection对象时，这个连接又可以拿出来用了。&lt;/p&gt; &lt;p&gt;既然我们每次都是从池中获取对象，那么这些对象是由谁来创建，又是什么时候创建的呢？这个就要根据不同情况由各对象池来自行实现了。例如，可以在创建对象池的时候指定池内对象数量，并且一下子全部创建好，当然您也可以在得到请求时，如果发现池中已经没有剩余对象时创建。您也可以“事前”先准备一部分，“事中”根据需要再继续补充。还可以做得“智能”一些，例如，根据实际情况添加或删除一些对象，甚至对需求“走势”进行“预测”，在空闲时便创建更多的对象以备“不时之需”。各中变化难以言尽。&lt;/p&gt; &lt;p&gt;当然，它们的原理和目的是类似的。相信上面这段文字也已经讲清了“线程池”的作用：因为创建一个线程的代价较高，因此我们使用线程池设法复用线程。就是这么简单。&lt;/p&gt; &lt;h1&gt;CLR线程池&lt;/h1&gt; &lt;p&gt;在.NET中，CLR线程和操作系统线程对应，您可以简单地认为.NET中的Thread对象便封装了一个操作系统线程，并附带一些托管环境下所需要的数据（如GC Handle）&lt;sup&gt;1&lt;/sup&gt;。而CLR线程池便是存放这些CLR线程的对象池。&lt;/p&gt; &lt;p&gt;我们在编写程序的时候，可以使用ThreadPool类的两个静态方法：QueueUserWorkItem和UnsafeUserQueueWorkItem向CLR线程池中添加任务（一个WorkCallback委托对象），这两个方法的区别，在于前者会收集调用方的ExecutionContext，也就是保留了的当前线程的执行信息（如认证或语言文化等），使任务最终会在“创建”时刻的环境中执行&lt;sup&gt;2&lt;/sup&gt;——后者就不会。因此，如果比较两个方法的绝对性能，Unsafe方法会略胜一筹。但是平时还是建议使用QueueUserWorkItem方法，因为保留执行上下文会避免很多麻烦事情，且这点性能损耗其实算不上什么。&lt;/p&gt; &lt;p&gt;CLR线程池在.NET框架中的作用很大，除了让程序员使用之外，其他一些功能也会依赖CLR线程池。如ThreadPool.RegisterWaitForSingleObject方法，或是System.Threading.Timer组件——还有更重要可能也是更隐藏的：ASP.NET在得到一个请求后，也会将这个请求处理的任务交由CLR线程池去执行——请注意，它们最多只是添加任务而已，并不表示任务会立即执行。所有添加到CLR线程池的任务都会在合适的时候得以执行——可能马上，也可能要稍等片刻，甚至更久。&lt;/p&gt; &lt;p&gt;向CLR线程池添加任务时，任务会被临时放到一个队列中，并在合适的时候执行。那么怎么样才算是“合适的时候”？简单的概括说来，便是线程池内有空闲的线程，或线程池所管理的线程数量还没有达到上限的时候。如果有空闲的线程，线程池就会立即让它领取一个任务执行。如果是第二种情况，线程池便会创建新的Thread对象。由于让操作系统管理太多线程反而会造成性能下降，因此CLR线程池会有一个上限。不同的托管环境会设置不同的上限。如在.NET 2.0 SP1之后，普通的Windows应用程序（如控制台或WinForm/WPF），会将其设置为“处理器数 * 250”。也就是说，如果您的机器为2个2核CPU，那么CLR线程池的容量默认上限便是1000，也就是说，它最多可以管理1000个线程同时运行——很多情况下这已经是一个很可怕的数字了，如果您觉得这还不够，那么就应该考虑一下您的实现方式是否可以改进了。&lt;/p&gt; &lt;p&gt;对于ASP.NET应用程序来说，CLR线程池容量代表了应用程序最多可以同时执行的请求数量。对于托管在IIS上的ASP.NET执行环境来说，这个值由全局配置决定。这个配置在machine.config文件中system.web/processModel节点中，为maxWorkerThreads属性，它决定了为单个处理器分配的线程数。如果这个值为40，且机器上拥有4个处理器（2 * 2CPU），那么这台机器目前的配置表示在同一时刻，ASP.NET可以同时处理160个请求。某些参考资料建议您将其修改为每处理器80-100个线程，这时您只要修改相应的属性值就可以了。&lt;/p&gt; &lt;p&gt;既然有最大值，也就相应有了最小值，它代表了CLR线程池“总是会保留”的最少线程数量。由于线程会占用资源，如在默认情况下，每个线程将获得1MB大小的栈空间&lt;sup&gt;3&lt;/sup&gt;。所以如果在系统中保留太多空闲线程对资源也是一种浪费。因此，CLR线程池在使用大量线程处理完大量任务之后，也会逐步地释放线程，直至到达最小值。CLR线程池的最小线程数量确保了在任务数量较少的情况下，新来的任务可以立即执行，从而省去了创建新线程的时间。在普通应用程序中这个值为“处理器数 * 1”，而在ASP.NET应用程序中这个值配置在machine.config文件中system.web/processModel节点的minWorkerThreads属性中&lt;sup&gt;4&lt;/sup&gt;。&lt;/p&gt; &lt;p&gt;在某些时候可能会遇到这样的情况：在一个瞬间忽然来大量任务，每个任务的执行时间说长不长说短不短，不过足以导致线程池快速分配数百个线程。如果这个峰值之后就一片平静，那么势必造成大量空闲的线程，这种开销对性能的损耗也非常明显。因此，CLR线程池限制了线程的创建速度不超过每秒2个。这样，即使在某个瞬时获得了大量的任务，CLR线程池也可以使用相对较少的线程来完成所有工作&lt;sup&gt;5&lt;/sup&gt;。&lt;/p&gt; &lt;p&gt;但是，还有一种情况也值得考虑。例如，对于一个比较繁忙的Web应用程序来说，一打开便会涌入大量的连接。由于线程的创建速度有限，因此可以执行的请求数量也只能慢慢增加。对于这种您预料到会产生大量线程，而且忙碌状况会持续一段时间的情况，限制线程的创建速度反而会带来损伤效率。这时，您就可以手动设置CLR线程池的最小线程数量。如果此时CLR线程池中拥有的线程数量较少，那么系统就会立即创建一定数量的线程来达到这个最小值。设置和获取CLR线程池最小线程数量的接口为：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;
{
    &lt;span style="color: blue"&gt;public static void &lt;/span&gt;GetMinThreads(&lt;span style="color: blue"&gt;out int &lt;/span&gt;workerThreads, &lt;span style="color: blue"&gt;out int &lt;/span&gt;completionPortThreads);
    &lt;span style="color: blue"&gt;public static bool &lt;/span&gt;SetMinThreads(&lt;span style="color: blue"&gt;int &lt;/span&gt;workerThreads, &lt;span style="color: blue"&gt;int &lt;/span&gt;completionPortThreads);
}&lt;/pre&gt;
&lt;p&gt;这两个接口的作用和使用方式应该足够明显了（不理解的话可以查阅MSDN），其中workerThreads参数便是CLR线程池的最小线程数，而completionPortThreads涉及到我们下次要讨论IO线程池，在此就不多作展开了。除了设置和读取CLR最小线程数的方法之外，ThreadPool还包含这些接口：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;
{
    &lt;span style="color: blue"&gt;public static void &lt;/span&gt;GetMaxThreads(&lt;span style="color: blue"&gt;out int &lt;/span&gt;workerThreads, &lt;span style="color: blue"&gt;out int &lt;/span&gt;completionPortThreads);
    &lt;span style="color: blue"&gt;public static bool &lt;/span&gt;SetMaxThreads(&lt;span style="color: blue"&gt;int &lt;/span&gt;workerThreads, &lt;span style="color: blue"&gt;int &lt;/span&gt;completionPortThreads);
    &lt;span style="color: blue"&gt;public static void &lt;/span&gt;GetAvailableThreads(&lt;span style="color: blue"&gt;out int &lt;/span&gt;workerThreads, &lt;span style="color: blue"&gt;out int &lt;/span&gt;completionPortThreads);
}&lt;/pre&gt;
&lt;p&gt;值得注意的是，无论是设置还是获取到的这些数值，都与处理器数量没有任何关系了。也就是说，在一台2 * 2CPU的机器上运行一个普通的.NET应用程序时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;调用GetMaxThreads方法将获得1000，表示CLR线程池最大容量为1000（250 * 4），而不是250。&lt;/li&gt;
&lt;li&gt;调用SetMinThreads并传入100，表示CLR线程池所拥有的最小线程数量为100，而不是400（100 * 4）。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;对于CLR线程池的简单描述就暂时先到这里了。如果您还有什么疑问请提出，我会加以补充。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;浅谈线程池（上）：线程池的作用及CLR线程池&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html"&gt;浅谈线程池（中）：独立线程池的作用及IO线程池&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/10/thread-pool-3-lab.html"&gt;浅谈线程池（下）：相关试验及注意事项&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注1：&lt;/strong&gt;严格说来，Thread对象和系统线程对应关系还有些细节上的考虑。例如，Thread对象只有当真正Start了之后，CLR才会创建一个操作系统线程与它绑定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注2：&lt;/strong&gt;ExecutionContext是个很重要且很有用的对象，例如，WinForms或WPF的异步任务中操作界面元素抛出异常该怎么办呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注3：&lt;/strong&gt;使用Windows API或Thread类创建线程时可以指定它的栈空间大小，但是CLR线程池中的线程只能使用默认值——不过这个默认值也和托管环境有关，如普通应用程序默认为1MB，而ASP.NET为250KB，这意味着ASP.NET应用程序相对更容易产生Stack Overflow异常。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注4：&lt;/strong&gt;可惜的是，对于processModel节点的数据，ASP.NET只会读取machine.config中的全局配置信息，这意味着我们不能使用web.config为不同应用程序配置不同的参数。如果我们要实现应用程序级别的配置，那么必须使用ThreadPool类中提供的API进行设置，这点稍后便会提到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注5：&lt;/strong&gt;对于这点，您不妨来做一个算术题：线程池内一下子涌入了500个任务，每个任务阻塞或暂停5秒，每个线程占用1MB内存，假设线程池目前为空，且有着足够的容量，此外线程创建速度也足够快，那么在限制及不限制线程创建速度的情况下，完成这些任务需要多少时间和内存空间？&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html#comments</comments>
      <pubDate>Wed, 22 Jul 2009 01:01:00 GMT</pubDate>
      <lastBuildDate>Wed, 22 Jul 2009 01:01:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>适合C# Actor的消息执行方式（4）：阶段性总结</title>
      <link>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html</link>
      <guid>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html</guid>
      <description>&lt;p&gt;这个系列原本打算写3篇，也就是说在上一篇文章中已经把老赵认为较合适的方法展现出来了，但事实上这个系列的计划已经扩展为8篇了——没错，翻了一倍不止，而最终是几篇现在我也无法断言。其实这也是我写不了书的原因之一。虽说唯一不变的就是变化，但是我变的太离谱了。不断写，不断出现新想法，不断变化。作为这个系列的第4篇，我们现在来对之前3篇文章进行一番阶段性总结。&lt;/p&gt; &lt;p&gt;阶段性总结本不在计划之内，不过似乎Actor模型这方面内容还不太受人关注，因此有的朋友也误解这系列文章想要解决的问题是什么。除了这方面的解释之外，我还会对之前提出的几种做法进行综合的对比，可以进一步了解整个演变过程的思路，为接下去的改变做铺垫——因为下次改变就涉及到多个方向，每个方向都是在一定程度上真正可用的方式。&lt;/p&gt; &lt;h1 id="whats-the-problem"&gt;我们究竟是要解决什么问题&lt;/h1&gt; &lt;p&gt;Actor模型的本质已经被强调了无数遍：万物皆Actor。Actor之间只有发送消息这一种通信方式，例如，无论是管理员让工作者干活，还是工作者把成果交还给管理员，它们之间也要通过发送消息的方式来传递信息。这么做看似不如直接方法调用来的直接，但是由于大量的消息可以同时执行。同样，消息让Actor之间解耦，消息发出之后执行成功还是失败，需要耗费多少时间，只要没有消息传递回来，这一切都和发送方无关。Actor模型的消息传递形式简化了并行程序的开发，使开发人员无需在共享内存（确切地说，其实是共享“写”）环境中与“锁”、“互斥体”等常用基础元素打交道。不过，使用Actor模型编写应用程序，需要开发人员使用一种与以往不同的设计思路，这样的思路说难倒不难，说简单也不简单。等我们有了成熟、稳固的Actor模型之后（例如高效的调度，合适的容错机制，老赵正在为此努力），再回头来探究这种特殊的架构方式。&lt;/p&gt; &lt;p&gt;由于Actor执行的唯一“事件”便是接受到了一个消息，而一个Actor很可能会做多件事情，因此我们一定需要一种机制，可以把消息“分派”到不同的“逻辑段”中去，并为不同的逻辑指定各自所需要的参数。例如，Person是一个Actor类型，它有三种任务，不同的任务会带有不同参数：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;聊天（Chat）：指定另一个Person对象（聊天的另一方），以及一个Topic对象（聊天的话题）。  &lt;li&gt;吃饭（Eat）：指定一个Restaurant对象（餐馆）。  &lt;li&gt;干活（Work）：指定一个Person对象（工作完成后的汇报人），以及一个Job对象（任务）。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;当Person对象获得一条消息时，它需要将其识别为聊天、吃饭或干活中的一种，再从中获取到这个行动所需要的数据。如果用一幅示意图来表示，它可能是这样的：&lt;/p&gt;&lt;img alt="message dispatch.png" src="http://docs.google.com/File?id=dgpjrmdf_110gt32tddb_b"&gt;  &lt;p&gt;&lt;font color="#ff0000"&gt;如何在C#中把一条消息转化为一段逻辑的执行，并且尽可能确保一些优势（如易于编写，静态检查，代码提示，重构，单元测试……）&lt;/font&gt;，这便是这系列文章唯一的目的。正如文章的标题，我们关注的是“消息执行方式”，而不是：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;“消息传递”与“共享内存”两种并行方式的比较  &lt;li&gt;讲述Actor模型的应用程序设计方式。  &lt;li&gt;提出消息传递时的解耦方式。  &lt;li&gt;……&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;文章使用Actor模型作为示例，是因为我编写的&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html"&gt;ActorLite组件&lt;/a&gt;易于说明问题，并且是典型的“消息传递”场景。事实上，文章所表达的内容，适合任何基于消息传递的C#场景，例如内存中的消息队列、生产者/消费者模式、消息总线……它并没有限制Actor模型这一种架构方式。&lt;/p&gt; &lt;h1&gt;Erlang的模式匹配&lt;/h1&gt; &lt;p&gt;首先，我们&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;观察了Erlang中的模式匹配&lt;/a&gt;。在Erlang中，一个消息往往为一个元组，而一个Actor便会根据这个消息的模式，或者用更通俗的方式来讲，“结构”，来选择处理消息的逻辑分支。例如对于上面举出的例子，它的模式匹配代码便可能是：&lt;/p&gt;&lt;pre class="code"&gt;receive
    {chat, Person, Topic} -&amp;gt;
        ... % “聊天”逻辑
    {eat, Restaurant} -&amp;gt;
        ... % “吃饭”逻辑
    {work, Person, Job} -&amp;gt;
        ... % “干活”逻辑
end&lt;/pre&gt;
&lt;p&gt;小写字母开头的标识符为“原子”，可以认为是一个常量，用于标识这个消息用来“干什么”。大写开头的为“绑定”，可以认为是一个变量（虽然不可变），用于标识这个消息“所使用的数据”。如果使用示意图来表示这个消息执行方式，则类似于：&lt;/p&gt;&lt;img alt="erlang pattern matching" src="http://docs.google.com/File?id=dgpjrmdf_1116qdqr4d7_b"&gt; 
&lt;p&gt;如果收到的消息是{eat, {mcdonalds, 2}}，则会执行“吃饭”逻辑，而执行时Restaurant的值将自动绑定为元组{mcdonalds, 2}，而不需要任何转化或赋值操作。Erlang便是这样将一个消息转化为一段逻辑执行的。&lt;/p&gt;
&lt;h1&gt;C#的Tag Message&lt;/h1&gt;
&lt;p&gt;一般来说，Erlang的消息是一个元组，而元组的第一个元素为原子，用来标识“做什么”。这个原子被称为是这个消息tag，这种用法被叫做Tag Message，它是“Erlang编程规范”中的推荐用法。在C#中，我们当然&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html#csharp-tag-message"&gt;也可以这么做&lt;/a&gt;：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Message &lt;/span&gt;message)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(message.Tag == &lt;span style="color: #a31515"&gt;"Chat"&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;another = (&lt;span style="color: #2b91af"&gt;Person&lt;/span&gt;)message.Arguments[0];
            &lt;span style="color: #2b91af"&gt;Topic &lt;/span&gt;topic = (&lt;span style="color: #2b91af"&gt;Topic&lt;/span&gt;)message.Arguments[1];
            &lt;span style="color: green"&gt;// ...
        &lt;/span&gt;}
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(message.Tag == &lt;span style="color: #a31515"&gt;"Eat"&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;Restaurant &lt;/span&gt;restaurant = (&lt;span style="color: #2b91af"&gt;Restaurant&lt;/span&gt;)message.Arguments[0];
            &lt;span style="color: green"&gt;// ...
        &lt;/span&gt;}
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(message.Tag == &lt;span style="color: #a31515"&gt;"Work"&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;reportTo = (&lt;span style="color: #2b91af"&gt;Person&lt;/span&gt;)message.Arguments[0];
            &lt;span style="color: #2b91af"&gt;Job &lt;/span&gt;job = (&lt;span style="color: #2b91af"&gt;Job&lt;/span&gt;)message.Arguments[1];
            &lt;span style="color: green"&gt;// ...
        &lt;/span&gt;}
    }
}&lt;/pre&gt;
&lt;p&gt;图示如下：&lt;/p&gt;&lt;img alt="message dispatch.png" src="http://docs.google.com/File?id=dgpjrmdf_112gc5dc6g9_b"&gt; 
&lt;p&gt;这个方式和Erlang可谓如出一辙，但是由于缺少了Erlang的模式匹配和自动绑定，于是C#代码需要大量的if…else判断，以及繁琐而危险的转型操作。此外，和Erlang中动态类型的缺点完全相同，无论是消息的发送还是接受完全不是静态类型的，因此无论是静态检查，编辑还是重构都比较困难。试想，如果一个公用的服务所接受的消息结构改变了，那么所有用到它的地方都必须修改正确——如果缺少静态检查，错误都只能在运行时才能发现。Erlang有着强大的动态升级能力，尚可接受不断地在线更新。而在.NET平台中，如果使用这种Tag Message的方式，待到运行时发现错误，要修改起来就比较麻烦了。&lt;/p&gt;
&lt;h1&gt;强类型消息&lt;/h1&gt;
&lt;p&gt;为了避免繁琐的转型，为了获得类型安全的各种优势，我们也可以选择为每种不同的消息创建独立的类型。不过由于一个Actor往往会应对各种消息，因此在.NET环境中，往往我们需要把消息类型定义为object。如果使用ActorLite来演示的话，代码可能是这样的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: blue"&gt;object &lt;/span&gt;message)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(message &lt;span style="color: blue"&gt;is &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ChatMessage&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;ChatMessage &lt;/span&gt;chatMsg = (&lt;span style="color: #2b91af"&gt;ChatMessage&lt;/span&gt;)message;
            &lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;another = chatMsg.Another;
            &lt;span style="color: #2b91af"&gt;Topic &lt;/span&gt;topic = chatMsg.Topic;
            &lt;span style="color: green"&gt;// ...
        &lt;/span&gt;}
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(message &lt;span style="color: blue"&gt;is &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EatMessage&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;EatMessage &lt;/span&gt;eatMsg = (&lt;span style="color: #2b91af"&gt;EatMessage&lt;/span&gt;)message;
            &lt;span style="color: #2b91af"&gt;Restaurant &lt;/span&gt;restaurant = eatMsg.Restaurant;
            &lt;span style="color: green"&gt;// ...
        &lt;/span&gt;}
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(message &lt;span style="color: blue"&gt;is &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WorkMessage&lt;/span&gt;)
        {
            &lt;span style="color: #2b91af"&gt;WorkMessage &lt;/span&gt;workMsg = (&lt;span style="color: #2b91af"&gt;WorkMessage&lt;/span&gt;)message;
            &lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;reportTo = workMsg.ReportTo;
            &lt;span style="color: #2b91af"&gt;Job &lt;/span&gt;job = workMsg.Job;
            &lt;span style="color: green"&gt;// ...
        &lt;/span&gt;}
    }
}&lt;/pre&gt;
&lt;p&gt;图示如下：&lt;/p&gt;&lt;img alt="string typing message" src="http://docs.google.com/File?id=dgpjrmdf_113dnxx2zdj_b"&gt; 
&lt;p&gt;使用if…else来进行逻辑分支判断还是必要的，不过我们这里使用了静态类型代替了Magic String（当然在使用Tag Message时也可以使用常量）的判断，同时危险而麻烦的类型转换操作也减少的。与Tag Message相比，这种做法获得了一定的类型安全优势，可以得到编译器的静态检查，做起重构来也有了依据。不过他也有比较明显的缺陷，那就是需要构建大量的消息类型。要知道消息类型的数量很可能是Actor类型数量的几倍，每种消息类型还包含着多个属性，构造函数接受参数，然后在构造函数里设置属性……这种做法对复杂性的提升还是较为可观的，有时候会感觉还不如使用简单的Tag Message。&lt;/p&gt;
&lt;h1&gt;接口、协议及消息&lt;/h1&gt;
&lt;p&gt;消息，其实是两个Actor之间定下的协议，一个Actor可以实现多种协议。这样的对应关系使人联想到.NET中的接口。因此我们可以使Actor实现某个接口，一条消息其实就是使用“委托”来告诉Actor该做什么事情。一个“委托”对象也可以自然地携带执行时所用的数据。这似乎满足我们的要求。使用这种方式来实现的消息执行&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html"&gt;大概是这样的&lt;/a&gt;：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPersonMessageHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Chat(&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;another, &lt;span style="color: #2b91af"&gt;Topic &lt;/span&gt;topic);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Eat(&lt;span style="color: #2b91af"&gt;Restaurant &lt;/span&gt;restaurant);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Work(&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;reportTo, &lt;span style="color: #2b91af"&gt;Job &lt;/span&gt;job);
}

&lt;span style="color: blue"&gt;class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPersonMessageHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPersonMessageHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPersonMessageHandler&lt;/span&gt;&amp;gt; message)
    {
        message(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;IPersonMessageHandler Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPersonMessageHandler&lt;/span&gt;.Chat(&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;another, &lt;span style="color: #2b91af"&gt;Topic &lt;/span&gt;topic) { ... }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPersonMessageHandler&lt;/span&gt;.Eat(&lt;span style="color: #2b91af"&gt;Restaurant &lt;/span&gt;restaurant) { ... }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPersonMessageHandler&lt;/span&gt;.Work(&lt;span style="color: #2b91af"&gt;Person &lt;/span&gt;reportTo, &lt;span style="color: #2b91af"&gt;Job &lt;/span&gt;job) { ... }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}&lt;/pre&gt;
&lt;p&gt;图示如下：&lt;/p&gt;&lt;img alt="interface contract" src="http://docs.google.com/File?id=dgpjrmdf_114gc73jnd6_b"&gt; 
&lt;p&gt;使用这种方式似乎带来的许多好处，例如我们使用接口这个非常轻量级的特性实现了消息，无须编写额外的代码将消息转化为逻辑。此外，接口是强类型的，适合编译期检查，易于重构和单元测试，还为我们带来“消息组”这样一种简单的消息管理方式——似乎就是我们理想的消息执行方式啊。是啊，这是很美好的消息执行方式，但是……为什么说它“中看不中用”？&lt;/p&gt;
&lt;p&gt;这里带来的最大问题在于耦合地过于强烈。例如Chat消息的第一个参数是Person，表示聊天的对象。但是很可能在同一个系统中，可以聊天的对象不一定仅限于是人（Person），还可能是一个机器人（Robot）；同理Work的汇报者也可能是一个记录系统。事实上，我们可能只要求Chat的目标是一个可以处理IChater消息的Actor即可，这意味着Chat方法的第一个参数的类型需要是Actor&amp;lt;Action&amp;lt;IChater&amp;gt;&amp;gt;。但是，这个Actor还需要接受其他类型的消息，如IWorkReportHandler，这又意味着它的类型需要是Actor&amp;lt;Action&amp;lt;IWorkResultHandler&amp;gt;&amp;gt;。一个Actor又如何成为两种类型？&lt;/p&gt;
&lt;p&gt;在实际运用中，这点无法回避，因此我们必须得变。&lt;/p&gt;
&lt;h1&gt;使用“消息总线”来解耦？&lt;/h1&gt;
&lt;p&gt;风云兄&lt;a href="http://www.cnblogs.com/netcasewqs/archive/2009/07/17/1525492.html"&gt;提出&lt;/a&gt;，既然问题的关键在于强耦合，为什么不使用消息总线来解耦呢？其实这问题很容易回答。&lt;/p&gt;
&lt;p&gt;首先，“强耦合”并不是我们想要解决的问题。我们想要解决的是“消息执行”（见文章标题）而不是“消息传递”，“强耦合”只是我们得到满意的解决方案之前所遇到的困难而已。“消息总线”是消息传递时，系统（大粒度）组件之间的解耦方式。而现在我们要解开的，是Actor这种小粒度对象之间消息传递造成的耦合。在.NET消息传递过程中，消息是一个对象，一般在框架中使用TMessage表示。例如风云兄给出的代码，TMessage即为字符串。我们的目标是如何从一个TMessage类型的对象（如字符串）分配至不同的逻辑片断——还要携带参数过去。风云兄的例子回避了这一点。&lt;/p&gt;
&lt;p&gt;事实上，正如&lt;a href="#whats-the-problem"&gt;文章一开始&lt;/a&gt;所说的那样，我们文章得出的解决方案并不仅限于Actor模型，它适合各种消息传递场景——这些场景里自然包括“消息总线”的使用。关于文章的目的，“&lt;a href="http://www.cnblogs.com/Alexander-Lee/"&gt;亚历山大同志&lt;/a&gt;”同学称之为“要在C#里面实现优雅的模式匹配的问题”。从一定角度上来说，老赵认为这个描述非常妥当，因为Erlang中模式匹配的目的便是消息执行。&lt;/p&gt;
&lt;p&gt;其实这可能也是基于Actor模型的程序架构方式还不为人熟悉的缘故。其实“消息总线”和“Actor模型”间的关系……不大，其相似性大概也只有“消息传递”这个特性而已。但是，在Actor模型中，消息是Actor对象间通信的唯一方式。Actor模型在使用时，内存中可能产生成千上万个Actor对象，它们互相发送消息产生交互。同时，Actor可以随时被创建出来，用完后又可以随时丢弃，因此Actor之间的通信无法“预先指定”。而“消息总线”需要在运行之前进行“注册”，然后它可以控制一条消息的Subject（即目标）。如果使用消息总线来实现Actor模型的话，则必须在Actor对象创建出来以后“注册”到消息总线，然后在Actor销毁之后“解开”。这又要求消息总线拥有高效的“注册”和“解开”操作，还必须完全是线程安全的。&lt;/p&gt;
&lt;p&gt;正是这个原因，Actor模型在使用时一般都是得到对方的引用并“直接”发送。而且，会把自己作为消息的一部分传递过去，这是为了让对方可以“直接”回复，这带来了程序设计过程中相当的灵活性。当然，这条消息可能会暂时放在Actor的队列中，等Actor空闲时再执行。这又是Actor模型的又一个特性：对于单个Actor来说，消息的执行完全是线程安全的。这大大简化了并行程序设计的难度，也是它与“共享内存”这种并行程序设计方式的区别。如果使用消息总线来实现Actor模型，它可以保证向一个Subject快速发送两条消息后，它们被依次执行吗？&lt;/p&gt;
&lt;p&gt;因此，如乒乓测试这种简单的消息传递示例，可以使用消息总线来实现，而复杂的场景就不合适了。在下一篇文章中，老赵会使用Actor模型来实现一个相对复杂的示例：网页小爬虫。在添加功能的过程中，您一定可以更好的了解Actor模型的使用方式，以及它在并行程序设计时的优势。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;适合C# Actor的消息执行方式（1）：Erlang中的模式匹配&lt;/a&gt;&lt;/li&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html"&gt;适合C# Actor的消息执行方式（2）：C# Actor的尴尬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html"&gt;适合C# Actor的消息执行方式（3）：中看不中用的解决方案&lt;/a&gt;&lt;/li&gt; 
&lt;li&gt;适合C# Actor的消息执行方式（4）：阶段性总结&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html"&gt;适合C# Actor的消息执行方式（5）：一个简单的网络爬虫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html"&gt;适合C# Actor的消息执行方式（6）：协变与逆变&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html#comments</comments>
      <pubDate>Mon, 20 Jul 2009 01:19:00 GMT</pubDate>
      <lastBuildDate>Mon, 20 Jul 2009 01:19:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>适合C# Actor的消息执行方式（3）：中看不中用的解决方案</title>
      <link>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html</link>
      <guid>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html</guid>
      <description>&lt;p&gt;在前两篇文章中，我们了解到&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;Erlang中灵活的模式匹配&lt;/a&gt;，以及在&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html"&gt;C#甚至F#中会都遭遇的尴尬局面&lt;/a&gt;。那么现在就应该来设计一个解决方案了，我们如何才能在C#这样的语言里顺畅地使用Actor模型呢？不仅如此，最好我们还能获得其它一些优势。&lt;/p&gt; &lt;h1&gt;“消息”、“协议”和“接口”&lt;/h1&gt; &lt;p&gt;Actor模型中的对象如果要进行交互，唯一的手段便是发送消息。不同语言/平台上的消息有不同的表现形式，但是它们所传递的信息是一致的：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;做什么事情  &lt;li&gt;做这件事情需要的数据&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;例如，Erlang中往往会使用Tag Message的格式作为消息：&lt;/p&gt;&lt;pre class="code"&gt;{doSomething, Arg1, Arg2, Arg3, ...}&lt;/pre&gt;
&lt;p&gt;其中，原子doSomthing表示“做什么”，而后面的ArgN便是一个个的参数，使用Erlang中的模式匹配可以很方便地捕获消息中的数据。在C#等语言中，由于并非专为了Actor模型设计，因此一个Message往往只能是一个对象。但是这个对象的职责并没有减轻，因此我们需要自己处理的事情就多了。我们可能会这样做：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;学Erlang的Tag Message，但是这样会产生大量丑陋的类型转换操作，并且丧失了静态检查功能。 
&lt;li&gt;为每种消息创建不同的Message类型，但是这样会产生大量类类型，每个类型又有各种属性，非常麻烦。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;这两种做法在上一篇文章里都有过讨论，感兴趣的朋友可以再去“回味”一番。那么，究竟什么是消息呢？根据我的理解，“消息”其实是这么一种东西：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“消息”表示“发送方”和“接受方”之间的“通信协议”（例如Erlang中的“模式”）。 
&lt;li&gt;“消息”表示“发送方”要“接受方”所做的事情，但是并没有要求“接受方”需要怎么做。 
&lt;li&gt;一个Actor可能会会作为“接受方”遵守多种“通信协议”。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;经过这样的描述，您是否觉得.NET中有一种东西和“消息”非常接近？没错，那就是“接口”，因为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;“接口”从概念上讲便是一种“协议”。 
&lt;li&gt;“接口”表示“能做什么”，但没有限制“怎么做”。 
&lt;li&gt;一个Actor可以实现多个接口，即遵守多种协议。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;看上去还真是一一对应啊！那么我们再来深入一步进行对比，“接口”能否传递消息所要表现的信息？答案也是肯定的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;做什么事情：接口中的一个方法。 
&lt;li&gt;需要的数据：接口的参数。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;也就是说，如之前的那条Erlang消息，在C#中便可以表示为：&lt;/p&gt;&lt;pre class="code"&gt;x.DoSomething(arg1, arg2, arg3, ...)&lt;/pre&gt;
&lt;p&gt;基于这样的类比，我们发现使用“接口”还可以带来一个额外的东西，那就是“消息组”。如Erlang这样语言，消息与消息之间是完全独立的。.NET中的接口可以包含多个方法，这就是一种“分组”，我们可以利用这种方式来更好地管理有关联的消息。此外，利用.NET中的访问限制符（public，internal等）还可以实现消息的公开和隐藏。而且因为接口的参数是强类型的，所以可以得到编译期的检查，也可以享受编辑工具的代码提示及重构……C#编程里的种种优势似乎我们一个都没有拉下。&lt;/p&gt;
&lt;h1&gt;看似美好的实现&lt;/h1&gt;
&lt;p&gt;等一下，接口只是一种“协议”，但是“消息”还必须是一个实体，一个对象，并且“携带”了这个协议才能在Actor之间传递啊。这个对象除了携带协议所需要的数据以外，还要能够告诉接受方究竟该“操作什么”。“操作”带上“数据”，于是我就想到了“委托”。例如，如果我们想要发送一个“协议”，叫做IDoHandler，那么我们便可以构造一个Action&amp;lt;IDoHandler&amp;gt;对象——这正是Lambda表达式的用武之地：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IDoHandler&lt;/span&gt;&amp;gt; m = x =&amp;gt; x.Do(0, 1, 2, ...);&lt;/pre&gt;
&lt;p&gt;好，那么我们还是用乒乓测试来尝试一番。我们知道，乒乓测试会让Ping对象和Pong对象相互发送消息，我们各使用一个“消息组”，也就是“接口”来定义消息：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler &lt;/span&gt;{ }

&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler &lt;/span&gt;{ }&lt;/pre&gt;
&lt;p&gt;那么，Ping和Pong两个Actor类型又该如何定义呢？我们知道，Ping需要处理Pong发来的消息，因此它需要实现IPongMessageHandler接口，并且需要接受类型为Action&amp;lt;IPongMessageHandler&amp;gt;的消息。Pong与Ping类似，因此它们的定义为：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Ping &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPongMessageHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;private int &lt;/span&gt;m_count;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;Ping(&lt;span style="color: blue"&gt;int &lt;/span&gt;count)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_count = count;
    }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler&lt;/span&gt;&amp;gt; message)
    {
        message(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    ...&lt;span style="color: blue"&gt;
&lt;/span&gt;}

&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Pong &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPingMessageHandler
&lt;/span&gt;{&lt;span style="color: blue"&gt;
    protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler&lt;/span&gt;&amp;gt; message)
    {
        message(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    ...
}&lt;/pre&gt;
&lt;p&gt;从代码上看，实际操作中我们并不需要让Ping或Pong直接继承Handler接口，只要最终提供一个对象给message执行即可。严格说来，“接口”只是一个“消息组”，具体的“消息”还是要落实到接口中的方法。定义了Ping和Pong之后，我们便可以明确接口中的方法了（确切地说，是明确了方法的参数）：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Pong(&lt;span style="color: #2b91af"&gt;Pong &lt;/span&gt;pong);
}

&lt;span style="color: blue"&gt;public interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Ping(&lt;span style="color: #2b91af"&gt;Ping &lt;/span&gt;ping);
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Finish();
}&lt;/pre&gt;
&lt;p&gt;使用了接口，自然就要提供方法的实现了。我们先从典型而简单的Pong对象看起：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Pong &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPingMessageHandler
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;IPingMessageHandler Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler&lt;/span&gt;.Ping(&lt;span style="color: #2b91af"&gt;Ping &lt;/span&gt;ping)
    {
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Pong received ping"&lt;/span&gt;);
        ping.Post(h =&amp;gt; h.Pong(&lt;span style="color: blue"&gt;this&lt;/span&gt;));
    }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPingMessageHandler&lt;/span&gt;.Finish()
    {
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Finished"&lt;/span&gt;);
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Exit();
    }

    &lt;span style="color: blue"&gt;#endregion&lt;/span&gt;
}&lt;/pre&gt;
&lt;p&gt;原本需要在得到消息之后，根据消息的内容作出不同的响应。而现在，消息会被自动转发为接口中的方法调用，我们只需要实现特定的方法即可。在Ping方法中，我们会得到一个Ping类型的对象——于是我们再向它回复一个消息。消息的类型是Action&amp;lt;IPongMessageHandler&amp;gt;，可以看出，使用Lambda表达式构造这样一个消息特别方便。&lt;/p&gt;
&lt;p&gt;Ping类也只需要实现IPongMessageHandler即可，只是这段逻辑“略显复杂”：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Ping &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler&lt;/span&gt;&amp;gt;&amp;gt;, &lt;span style="color: #2b91af"&gt;IPongMessageHandler
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Start(&lt;span style="color: #2b91af"&gt;Pong &lt;/span&gt;pong)
    {
        pong.Post(h =&amp;gt; h.Ping(&lt;span style="color: blue"&gt;this&lt;/span&gt;));
    }

    &lt;span style="color: blue"&gt;#region &lt;/span&gt;IPongMessageHandler Members

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler&lt;/span&gt;.Pong(&lt;span style="color: #2b91af"&gt;Pong &lt;/span&gt;pong)
    {
        &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Ping received pong"&lt;/span&gt;);

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(--&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_count &amp;gt; 0)
        {
            pong.Post(h =&amp;gt; h.Ping(&lt;span style="color: blue"&gt;this&lt;/span&gt;));
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            pong.Post(h =&amp;gt; h.Finish());
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Exit();
        }
    }

    &lt;span style="color: blue"&gt;#endregion
&lt;/span&gt;}&lt;/pre&gt;
&lt;p&gt;收到Pong消息之后，将count减1，如果还大于0，则回复一个Ping消息，否则就回复一个Finish并退出。最后启动乒乓测试：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Ping&lt;/span&gt;(5).Start(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Pong&lt;/span&gt;());&lt;/pre&gt;
&lt;p&gt;由于使用了接口作为消息的协议，因此无论是编辑器还是编译器都可以给我们足够的支持。同时，对于消息的处理也无须如上一篇文章那样不断进行判断和类型转换，代码可谓流畅不少。&lt;/p&gt;
&lt;h1&gt;致命的缺陷&lt;/h1&gt;
&lt;p&gt;虽说没有完美的东西，但目前的缺陷却是致命的。&lt;/p&gt;
&lt;p&gt;在实际使用过程中，消息的“发送方”和消息的“接收方”应该完全无关，它们互不知道对方具体是谁，只应该基于“协议”，也就是“接口”来实现。可惜在上面这段代码中，很多东西都被“强横”地限制住了。例如，Ping消息会附带一个ping对象作为参数，ping对象会等待一个Pong消息。但是，发送Ping消息（并等待Pong消息）的一方很可能是各种类型的Actor，不一定是Ping类型。有朋友可能会说，那么我们把IPingMessageHandler的Ping方法的签名改成这样，不就可以了吗？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;void &lt;/span&gt;Ping(&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Action&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;IPongMessageHandler&lt;/span&gt;&amp;gt;&amp;gt; ping)&lt;/pre&gt;
&lt;p&gt;是的，此时的ping，的确是在“等待Pong消息的Actor对象”。但是，这意味着ping对象它也只能是这个指明的Actor类型了。在实际使用过程中，这几乎是不可能的事情。因为一个Actor很可能会接受各种消息，它很难做到“一心一意”。因此这篇文章所提出的做法，几乎只能满足如乒乓测试这样简单的Actor模型使用场景。我们必须改变。&lt;/p&gt;
&lt;p&gt;改变的方式有不少，从“向弱类型妥协”到“利用.NET 4.0中的协变/逆变”，都可以满足不同的场景——不过我们还是下次再说吧。&lt;/p&gt;
&lt;h1&gt;F#的实现&lt;/h1&gt;
&lt;p&gt;本文描述的方式也可以运用在在F#中。首先自然还是接口的定义：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;IPingMessageHandler =
    &lt;span style="color: blue"&gt;abstract &lt;/span&gt;Ping : Ping &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;unit
    &lt;span style="color: blue"&gt;abstract &lt;/span&gt;Finish : unit &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;unit

&lt;span style="color: blue"&gt;and &lt;/span&gt;IPongMessageHandler = 
    &lt;span style="color: blue"&gt;abstract &lt;/span&gt;Pong : Pong &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;unit&lt;/pre&gt;
&lt;p&gt;以上便是F#中定义接口的方式，与C#相比更为简洁。接着便是Ping类型的实现：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;and &lt;/span&gt;Ping() =
    &lt;span style="color: blue"&gt;inherit &lt;/span&gt;(IPongMessageHandler &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;unit) Actor()
    &lt;span style="color: blue"&gt;let mutable &lt;/span&gt;count = 5
    &lt;span style="color: blue"&gt;override &lt;/span&gt;self.Receive(message) = message self

    &lt;span style="color: blue"&gt;member &lt;/span&gt;self.Start(pong : Pong) = 
        pong &amp;lt;&amp;lt; &lt;span style="color: blue"&gt;fun &lt;/span&gt;h &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;self |&amp;gt; h.Ping
    
    &lt;span style="color: blue"&gt;interface &lt;/span&gt;IPongMessageHandler &lt;span style="color: blue"&gt;with 
        member &lt;/span&gt;self.Pong(pong) =
            printfn &lt;span style="color: maroon"&gt;"Ping received pong"
            &lt;/span&gt;count &amp;lt;- count - 1
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(count &amp;gt; 0) &lt;span style="color: blue"&gt;then
                &lt;/span&gt;pong &amp;lt;&amp;lt; &lt;span style="color: blue"&gt;fun &lt;/span&gt;h &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;self |&amp;gt; h.Ping
            &lt;span style="color: blue"&gt;else
                &lt;/span&gt;pong &amp;lt;&amp;lt; &lt;span style="color: blue"&gt;fun &lt;/span&gt;h &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;h.Finish()
                self.Exit()&lt;/pre&gt;
&lt;p&gt;Pong类型的实现则更为简单：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;and &lt;/span&gt;Pong() =
    &lt;span style="color: blue"&gt;inherit &lt;/span&gt;(IPingMessageHandler &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;unit) Actor()
    &lt;span style="color: blue"&gt;override &lt;/span&gt;self.Receive(message) = message self
    
    &lt;span style="color: blue"&gt;interface &lt;/span&gt;IPingMessageHandler &lt;span style="color: blue"&gt;with
        member &lt;/span&gt;self.Ping(ping) =
            printfn &lt;span style="color: maroon"&gt;"Pong received ping"
            &lt;/span&gt;ping &amp;lt;&amp;lt; &lt;span style="color: blue"&gt;fun &lt;/span&gt;h &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;self |&amp;gt; h.Pong
        
        &lt;span style="color: blue"&gt;member &lt;/span&gt;self.Finish() =
            printfn &lt;span style="color: maroon"&gt;"Finished"
            &lt;/span&gt;self.Exit()&lt;/pre&gt;
&lt;p&gt;启动乒乓测试：&lt;/p&gt;&lt;pre class="code"&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;Pong()) |&amp;gt; (&lt;span style="color: blue"&gt;new &lt;/span&gt;Ping()).Start;&lt;/pre&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;适合C# Actor的消息执行方式（1）：Erlang中的模式匹配&lt;/a&gt; 
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html"&gt;适合C# Actor的消息执行方式（2）：C# Actor的尴尬&lt;/a&gt; 
&lt;li&gt;适合C# Actor的消息执行方式（3）：中看不中用的解决方案&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html"&gt;适合C# Actor的消息执行方式（4）：阶段性总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html"&gt;适合C# Actor的消息执行方式（5）：一个简单的网络爬虫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html"&gt;适合C# Actor的消息执行方式（6）：协变与逆变&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;本文代码访问地址：&lt;a href="http://gist.github.com/148464"&gt;http://gist.github.com/148464&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html#comments</comments>
      <pubDate>Fri, 17 Jul 2009 00:45:00 GMT</pubDate>
      <lastBuildDate>Fri, 17 Jul 2009 00:45:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>适合C# Actor的消息执行方式（2）：C# Actor的尴尬</title>
      <link>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html</link>
      <guid>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html</guid>
      <description>&lt;p&gt;在&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;上一篇文章&lt;/a&gt;中，我们简单解读了Erlang在执行消息时候的方式。而现在，我们就一起来看看，C# Actor究竟出现了什么样的尴尬。此外，我还打算用F#进行补充说明，最终我们会发现，虽然&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-3.html"&gt;F#看上去很美&lt;/a&gt;，但是在实际使用过程中依旧有些遗憾。&lt;/p&gt; &lt;h1&gt;Erlang中的Tag Message&lt;/h1&gt; &lt;p&gt;老赵在上一篇文章里提到，Erlang中有一个“约定俗成”，使用“原子（atom）”来表示这条消息“做什么”，并使用“绑定（binding）”来获取做事情所需要的“参数”。Erlang大拿，《Programming Erlang》一书的主要译者&lt;a href="http://erlang-china.org/"&gt;jackyz同学&lt;/a&gt;看了老赵的文章后&lt;a href="http://erlang-china.org/study/puzzle-in-erlang_pattern_match.html"&gt;指出&lt;/a&gt;，这一点在&lt;a href="http://www.erlang.se/doc/programming_rules.shtml"&gt;Erlang编程规范&lt;/a&gt;中有着明确的说法，是为“Tag Message”：&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&lt;strong&gt;5.7 Tag messages&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;All messages should be tagged. This makes the order in the receive statement less important and the implementation of new messages easier.&lt;/p&gt; &lt;p&gt;Don’t program like this:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;loop&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;State&lt;/span&gt;&lt;span style="color: olive"&gt;)&lt;/span&gt;&lt;span style="color: gray"&gt; -&amp;gt;&lt;br&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green"&gt;receive&lt;/span&gt;&lt;span style="color: gray"&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/span&gt;&lt;span style="color: blue"&gt;Mod&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Funcs&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Args&lt;/span&gt;&lt;span style="color: gray"&gt;} -&amp;gt; &lt;/span&gt;&lt;span style="color: #ffa500"&gt;% Don't do this&lt;/span&gt;&lt;span style="color: gray"&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: blue"&gt;apply&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;Mod&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Funcs&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Args&lt;/span&gt;&lt;span style="color: gray"&gt;},&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: blue"&gt;loop&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;State&lt;/span&gt;&lt;span style="color: olive"&gt;)&lt;/span&gt;&lt;span style="color: gray"&gt;;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green"&gt;end&lt;/span&gt;&lt;span style="color: gray"&gt;.&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;The new message {get_status_info, From, Option} will introduce a conflict if it is placed below the {Mod, Func, Args} message.&lt;/p&gt;
&lt;p&gt;If messages are synchronous, the return message should be tagged with a new atom, describing the returned message. Example: if the incoming message is tagged get_status_info, the returned message could be tagged status_info. One reason for choosing different tags is to make debugging easier.&lt;/p&gt;
&lt;p&gt;This is a good solution:&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;loop&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;State&lt;/span&gt;&lt;span style="color: olive"&gt;)&lt;/span&gt;&lt;span style="color: gray"&gt; -&amp;gt;&lt;br&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green"&gt;receive&lt;/span&gt;&lt;span style="color: gray"&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/span&gt;&lt;span style="color: blue"&gt;execute&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Mod&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Funcs&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Args&lt;/span&gt;&lt;span style="color: gray"&gt;} -&amp;gt; &lt;/span&gt;&lt;span style="color: #ffa500"&gt;% Use a tagged message.&lt;/span&gt;&lt;span style="color: gray"&gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: blue"&gt;apply&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;Mod&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Funcs&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Args&lt;/span&gt;&lt;span style="color: gray"&gt;},&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: blue"&gt;loop&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;State&lt;/span&gt;&lt;span style="color: olive"&gt;)&lt;/span&gt;&lt;span style="color: gray"&gt;;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;/span&gt;&lt;span style="color: blue"&gt;get_status_info&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;From&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;Option&lt;/span&gt;&lt;span style="color: gray"&gt;} -&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: blue"&gt;From&lt;/span&gt;&lt;span style="color: gray"&gt; ! {&lt;/span&gt;&lt;span style="color: blue"&gt;status_info&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;get_status_info&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;Option&lt;/span&gt;&lt;span style="color: gray"&gt;, &lt;/span&gt;&lt;span style="color: blue"&gt;State&lt;/span&gt;&lt;span style="color: olive"&gt;)&lt;/span&gt;&lt;span style="color: gray"&gt;},&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;span style="color: blue"&gt;loop&lt;/span&gt;&lt;span style="color: olive"&gt;(&lt;/span&gt;&lt;span style="color: blue"&gt;State&lt;/span&gt;&lt;span style="color: olive"&gt;)&lt;/span&gt;&lt;span style="color: gray"&gt;;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br&gt;&amp;nbsp; &lt;/span&gt;&lt;span style="color: green"&gt;end&lt;/span&gt;&lt;span style="color: gray"&gt;.&lt;/span&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;p&gt;第一段代码使用的模式为拥有三个“绑定”的“元组”。由于Erlang的弱类型特性，任何拥有三个元素的元组都会被匹配到，这不是一个优秀的实践。在第二个示例中，每个模式使用一个“原子”来进行约束，这样可以获取到相对具体的消息。为什么说“相对”？还是因为Erlang的弱类型特性，Erlang无法对From和Option提出更多的描述。同样它也无法得知execute或get_status_info这两个tag的来源——当然，在许多时候，它也不需要关心是谁发送给它的。&lt;/p&gt;
&lt;h1 id="csharp-tag-message"&gt;在C#中使用Tag Message&lt;/h1&gt;
&lt;p&gt;在C#中模拟Erlang里的Tag Message很简单，其实就是把每条消息封装为Tag和参数列表的形式。同样的，我们使用的都是弱类型的数据——也就是object类型。如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Message
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public object &lt;/span&gt;Tag { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReadOnlyCollection&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt; Arguments { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;Message(&lt;span style="color: blue"&gt;object &lt;/span&gt;tag, &lt;span style="color: blue"&gt;params object&lt;/span&gt;[] arguments)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Tag = tag;
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Arguments = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ReadOnlyCollection&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;object&lt;/span&gt;&amp;gt;(arguments);
    }
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;我们可以使用这种方式来实现一个乒乓测试。既然是Tag Message，那么定义一些Tag便是首要任务。Tag表示“做什么”，即消息的“功能”。在乒乓测试中，有两种消息，共三个“含义”。Erlang使用原子作为tag，在.NET中我们自然可以使用枚举：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public enum &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PingMsg
&lt;/span&gt;{ 
    Finished,
    Ping
}

&lt;span style="color: blue"&gt;public enum &lt;/span&gt;&lt;span style="color: #2b91af"&gt;PongMsg
&lt;/span&gt;{ 
    Pong
}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;在这里，我们使用简单的ActorLite进行演示（请参考&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-2.html#usage"&gt;ActorLite的使用方式&lt;/a&gt;）。因此，Ping和Pong均继承于Actor&amp;lt;Message&amp;gt;类，并实现其Receive方法。&lt;/p&gt;
&lt;p&gt;对于Ping对象来说，它会维护一个计数器。每当收到PongMsg.Pong消息后，会将计数器减1。如果计数器为0，则回复一条PingMsg.Finished消息，否则就回复一个PingMsg.Ping：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Ping &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;private int &lt;/span&gt;m_count;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;Ping(&lt;span style="color: blue"&gt;int &lt;/span&gt;count)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_count = count;
    }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Start(&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;&amp;gt; pong)
    {
        pong.Post(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;PingMsg&lt;/span&gt;.Ping, &lt;span style="color: blue"&gt;this&lt;/span&gt;));
    }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Message &lt;/span&gt;message)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(message.Tag.Equals(&lt;span style="color: #2b91af"&gt;PongMsg&lt;/span&gt;.Pong))
        {
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Ping received pong"&lt;/span&gt;);

            &lt;span style="color: blue"&gt;var &lt;/span&gt;pong = message.Arguments[0] &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;&amp;gt;;
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(--&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_count &amp;gt; 0)
            {
                pong.Post(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;PingMsg&lt;/span&gt;.Ping, &lt;span style="color: blue"&gt;this&lt;/span&gt;));
            }
            &lt;span style="color: blue"&gt;else
            &lt;/span&gt;{
                pong.Post(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;PingMsg&lt;/span&gt;.Finished));
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.Exit();
            }
        }
    }
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;对于Pong对象来说，如果接受到PingMsg.Ping消息，则回复一个PongMsg.Pong。如果接受的消息为PingMsg.Finished，便立即退出：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Pong &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: #2b91af"&gt;Message &lt;/span&gt;message)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(message.Tag.Equals(&lt;span style="color: #2b91af"&gt;PingMsg&lt;/span&gt;.Ping))
        {
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Pong received ping"&lt;/span&gt;);

            &lt;span style="color: blue"&gt;var &lt;/span&gt;ping = message.Arguments[0] &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;&amp;gt;;
            ping.Post(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Message&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;PongMsg&lt;/span&gt;.Pong, &lt;span style="color: blue"&gt;this&lt;/span&gt;));
        }
        &lt;span style="color: blue"&gt;else if &lt;/span&gt;(message.Tag.Equals(&lt;span style="color: #2b91af"&gt;PingMsg&lt;/span&gt;.Finished))
        {
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"Finished"&lt;/span&gt;);
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Exit();
        }
    }
}&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;启动乒乓测试：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Ping&lt;/span&gt;(5).Start(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Pong&lt;/span&gt;());&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;结果如下：&lt;/p&gt;&lt;pre class="code"&gt;Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Finished&lt;/pre&gt;
&lt;p&gt;从上述代码中可以看出，由于没有Erlang的模式匹配，我们必须使用if…else…的方式来判断消息的Tag，接下来还必须使用麻烦而危险的cast操作来获取参数。更令人尴尬的是，与Erlang相比，在C#中使用Tag Message没有获得任何好处。同样是弱类型，同样得不到静态检查。那么好处在哪里？至少我的确看不出来。&lt;/p&gt;
&lt;p&gt;有朋友可能会说，C#既然是一门强类型的语言，为什么要学Erlang的Tag Message？为什么不把Ping定义为Actor&amp;lt;PingMessage&amp;gt;，同时把Pong定义为Actor&amp;lt;PingMessage&amp;gt;呢？&lt;/p&gt;
&lt;p&gt;呃……我承认，在这里使用Tag Message的确有种“画虎不成反类犬”的味道。不过，事情也不是您想象的那么简单。因为在实际情况中，一个Actor可能与各种外部服务打交道，它会接受到各式各样的消息。例如，它先向Service Locator发送一个请求，用于查询数据服务的位置，这样它会接受到一个ServiceLocatorResponse消息。然后，它会向数据服务发送一个请求，再接受到一个DataAccessResponse消息。也就是说，很可能我们必须把每个Actor都定义为Actor&amp;lt;object&amp;gt;，然后对消息进行类型判断，转换，再加以处理。&lt;/p&gt;
&lt;p&gt;诚然，这种方法相对于Tag Message拥有了一定的强类型优势（如静态检查）。但是如果您选择这么做，就必须为各种消息定义不同的类型，在这方面会带来额外的开发成本。要知道，消息的数量并不等于Actor类型的数量，即使是如Ping这样简单的Actor，都会发送两种不同的消息（Ping和Finished），而且每种消息拥有各自的参数。一般来说，某个Actor会接受2-3种消息都是比较正常的状况。在面对消息类型的汪洋时，您可能就会怀念Tag Message这种做法了。到时候您可能就会发牢骚说：&lt;/p&gt;
&lt;p&gt;“弱类型就弱类型吧，Erlang不也用的好好的么……”&lt;/p&gt;
&lt;h1&gt;F#中的模式匹配&lt;/h1&gt;
&lt;p&gt;提到模式匹配，熟悉F#的同学们可能会欢喜不已。模式匹配是F#中的重要特性，它将F#中静态类型系统的灵活性体现地淋漓尽致。而且——它还很能节省代码（这点&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-3.html#fsharp"&gt;在老赵以前的文章中也有所提及&lt;/a&gt;）。那么我们再来看一次F#在乒乓测试中的表现。&lt;/p&gt;
&lt;p&gt;首先还是定义PingMsg和PongMsg：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;PingMsg = 
    | Ping &lt;span style="color: blue"&gt;of &lt;/span&gt;PongMsg Actor
    | Finished
&lt;span style="color: blue"&gt;and &lt;/span&gt;PongMsg = 
    | Pong &lt;span style="color: blue"&gt;of &lt;/span&gt;PingMsg Actor&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;这里体现了F#类型系统中的Discriminated Unions。简单地说，它的作用是把一种类型定义为多种表现形式，这个特性在Haskell等编程语言中非常常见。Discriminated Unions非常适合模式匹配，现在的ping对象和pong对象便可定义如下（在这里还是使用了ActorLite，而不是F#标准库中的MailboxProcessor来实现Actor模型）：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;(&amp;lt;&amp;lt;) (a:_ Actor) msg = a.Post msg

&lt;span style="color: blue"&gt;let &lt;/span&gt;ping =
    &lt;span style="color: blue"&gt;let &lt;/span&gt;count = ref 5
    { &lt;span style="color: blue"&gt;new &lt;/span&gt;PongMsg Actor() &lt;span style="color: blue"&gt;with
        override &lt;/span&gt;self.Receive(message) =
            &lt;span style="color: blue"&gt;match &lt;/span&gt;message &lt;span style="color: blue"&gt;with
            &lt;/span&gt;| Pong(pong) &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"Ping received pong"
                &lt;/span&gt;count := !count - 1
                &lt;span style="color: blue"&gt;if &lt;/span&gt;(!count &amp;gt; 0) &lt;span style="color: blue"&gt;then
                    &lt;/span&gt;pong &amp;lt;&amp;lt; Ping(self)
                &lt;span style="color: blue"&gt;else
                    &lt;/span&gt;pong &amp;lt;&amp;lt; Finished
                    self.Exit() }

&lt;span style="color: blue"&gt;let &lt;/span&gt;pong = 
    { &lt;span style="color: blue"&gt;new &lt;/span&gt;PingMsg Actor() &lt;span style="color: blue"&gt;with
        override &lt;/span&gt;self.Receive(message) =
            &lt;span style="color: blue"&gt;match &lt;/span&gt;message &lt;span style="color: blue"&gt;with
            &lt;/span&gt;| Ping(ping) &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;printfn &lt;span style="color: maroon"&gt;"Pong received ping"
                &lt;/span&gt;ping &amp;lt;&amp;lt; Pong(self)
            | Finished &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;printf &lt;span style="color: maroon"&gt;"Fininshed"
                &lt;/span&gt;self.Exit() }&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;例如在pong对象的实现中，我们使用模式匹配，减少了不必要的类型转换和赋值，让代码变得简洁易读。还有一点值得顺带一提，我们在F#中可以灵活的定义一个操作符的作用，在这里我们便把“&amp;lt;&amp;lt;”定义为“发送”操作，避免Post方法的显式调用。这种做法往往可以简化代码，从语义上增强了代码的可读性。例如，我们可以这样启动乒乓测试：&lt;/p&gt;&lt;pre class="code"&gt;ping &amp;lt;&amp;lt; Pong(pong)&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;至于结果则与C#的例子一模一样，就不再重复了。&lt;/p&gt;
&lt;h1&gt;F#中的弱类型消息&lt;/h1&gt;
&lt;p&gt;可是，F#的世界就真的如此美好吗？试想，我们该如何实现一个需要接受多种不同消息的Actor对象呢？我们只能这样做：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;another = 
    { &lt;span style="color: blue"&gt;new &lt;/span&gt;obj Actor() &lt;span style="color: blue"&gt;with
        override &lt;/span&gt;self.Receive(message) =
            &lt;span style="color: blue"&gt;match &lt;/span&gt;message &lt;span style="color: blue"&gt;with
            
            &lt;/span&gt;| :? PingMsg &lt;span style="color: blue"&gt;as &lt;/span&gt;pingMsg &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;&lt;span style="color: green"&gt;// sub matching
                &lt;/span&gt;&lt;span style="color: blue"&gt;match &lt;/span&gt;pingMsg &lt;span style="color: blue"&gt;with
                &lt;/span&gt;| Ping(pong) &lt;span style="color: blue"&gt;-&amp;gt; null &lt;/span&gt;|&amp;gt; ignore
                | Finished &lt;span style="color: blue"&gt;-&amp;gt; null &lt;/span&gt;|&amp;gt; ignore
                
            | :? PongMsg &lt;span style="color: blue"&gt;as &lt;/span&gt;pongMsg &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;&lt;span style="color: green"&gt;// sub matching
                &lt;/span&gt;&lt;span style="color: blue"&gt;match &lt;/span&gt;pongMsg &lt;span style="color: blue"&gt;with
                &lt;/span&gt;| Pong(ping) &lt;span style="color: blue"&gt;-&amp;gt; null &lt;/span&gt;|&amp;gt; ignore
                
            | :? (string * int) &lt;span style="color: blue"&gt;as &lt;/span&gt;m &lt;span style="color: blue"&gt;-&amp;gt;
                &lt;/span&gt;&lt;span style="color: green"&gt;// sub binding
                &lt;/span&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;(s, i) = m
                &lt;span style="color: blue"&gt;null &lt;/span&gt;|&amp;gt; ignore
                
            | _ &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;failwith &lt;span style="color: maroon"&gt;"Unrecognized message" &lt;/span&gt;}&lt;/pre&gt;
&lt;p&gt;由于我们必须使用object作为Actor接受到的消息类型，因此我们在对它作模式匹配时，只能进行参数判断。如果您要更进一步地“挖掘”其中的数据，则很可能需要进行再一次的模式匹配（如PingMsg或PongMsg）或赋值（如string * int元组）。一旦出现这种情况，在我看来也变得不是那么理想了，我们既没有节省代码，也没有让代码变得更为易读。与C#相比，唯一的优势可能就是F#中相对灵活的类型系统吧。&lt;/p&gt;
&lt;p&gt;C#不好用，F#也不行……那么我们又该怎么办？&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html"&gt;适合C# Actor的消息执行方式（1）：Erlang中的模式匹配&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;适合C# Actor的消息执行方式（2）：C# Actor的尴尬&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html"&gt;适合C# Actor的消息执行方式（3）：中看不中用的解决方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html"&gt;适合C# Actor的消息执行方式（4）：阶段性总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html"&gt;适合C# Actor的消息执行方式（5）：一个简单的网络爬虫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html"&gt;适合C# Actor的消息执行方式（6）：协变与逆变&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html#comments</comments>
      <pubDate>Sun, 12 Jul 2009 16:24:00 GMT</pubDate>
      <lastBuildDate>Sun, 12 Jul 2009 16:24:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>适合C# Actor的消息执行方式（1）：Erlang中的模式匹配</title>
      <link>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html</link>
      <guid>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html</guid>
      <description>&lt;h1&gt;前言&lt;/h1&gt; &lt;p&gt;&lt;a href="http://en.wikipedia.org/wiki/Actor_model"&gt;Actor模型&lt;/a&gt;为并行而生。由于现在单台机器中独立的计算单元也越来越多，Actor模型的重要性也越来越大。Actor模型的理念非常简单：天下万物皆为Actor，Actor之间通过发送消息进行通信。不同的Actor可以同时处理各自的消息，从而获得了大规模的并发能力。&lt;/p&gt; &lt;p&gt;Erlang基于Actor模型实现，我们甚至可以这样认为，没有Erlang在业界竖立的丰碑，Actor模型便不会如此受人关注。目前，几乎所有的主流开发平台上都有了Actor模型的实现，如Java平台下的&lt;a href="http://code.google.com/p/jetlang/"&gt;Jetlang&lt;/a&gt;以及.NET平台下的&lt;a href="http://msdn.microsoft.com/en-us/library/bb648752.aspx"&gt;MS CCR&lt;/a&gt;和&lt;a href="http://code.google.com/p/retlang/"&gt;Retlang&lt;/a&gt;；还有一些Actor框架专为特定语言设计，如&lt;a href="http://en.wikibooks.org/wiki/F_Sharp_Programming/MailboxProcessor"&gt;F#的MailboxProcessor&lt;/a&gt;以及&lt;a href="http://www.scala-lang.org/node/242"&gt;Scala的Actor类库&lt;/a&gt;；甚至微软还基于MS CCR构建了一门新的语言&lt;a href="http://msdn.microsoft.com/en-us/devlabs/dd795202.aspx"&gt;Axum&lt;/a&gt;。&lt;/p&gt; &lt;p&gt;不过对于.NET平台下的开发人员来说，我们最常用的语言是C#。无论您是在使用MS CCR还是Retlang（亦或是我写的&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html"&gt;ActorLite&lt;/a&gt;），在消息的执行阶段总是略显尴尬。本文的目的便是提出一种适合C# Actor的消息执行方式，而这种执行方式还会成为我以后公开的C#中“模式匹配”的基础。&lt;/p&gt; &lt;h1&gt;Erlang中的执行方式&lt;/h1&gt; &lt;p&gt;本文将分为三个部分，您目前正在阅读的第一部分，将会观察Erlang是如何执行消息的。有对比才会有差距，也正是由于Erlang在Actor模型上的示范作用，我们才会意识到C# Actor在使用上有多么的不方便。&lt;/p&gt; &lt;p&gt;作为示例，我们还是使用最经典的&lt;a href="http://blog.zhaojie.me/2009/06/everything-ping-pong.html"&gt;乒乓测试&lt;/a&gt;。乒乓测试的效果很简单：ping和pong为两个Actor对象，首先由ping向pong发送一个“Ping”消息，pong在接受到Ping消息之后，将会向ping发送一个“Pong”消息。在双方“乒来乓去”几个回合后，ping将会向pong发起“Finished”，从而停止交互。&lt;/p&gt; &lt;p&gt;乒乓测试的Erlang的实现代码如下：&lt;/p&gt;&lt;pre class="code"&gt;-module(tut15).

-export([start/0, ping/2, pong/0]).

ping(0, Pong_PID) -&amp;gt;
    Pong_PID ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_PID) -&amp;gt;
    Pong_PID ! {ping, self()},
    receive
        pong -&amp;gt;
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_PID).

pong() -&amp;gt;
    receive
        finished -&amp;gt;
            io:format("Pong finished~n", []);
        {ping, Ping_PID} -&amp;gt;
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start() -&amp;gt;
    Pong_PID = spawn(tut15, pong, []),
    spawn(tut15, ping, [3, Pong_PID]).&lt;/pre&gt;
&lt;p&gt;由于Erlang的&lt;a href="http://en.wikipedia.org/wiki/Functional_programming"&gt;函数式编程&lt;/a&gt;，&lt;a href="http://blog.zhaojie.me/2009/03/tail-recursion-and-continuation.html"&gt;尾递归&lt;/a&gt;，receive原语等特殊的语言特性，其乒乓测试的实现或语义上可能和其他语言有一定区别（详见《&lt;a href="http://blog.zhaojie.me/2009/06/everything-ping-pong.html"&gt;天下无处不乒乓&lt;/a&gt;》一文）。不过我们现在还是关注Erlang在消息执行时的特性：模式匹配。&lt;/p&gt;
&lt;p&gt;虽然Erlang有诸多优秀特性，但是它的数据抽象能力非常有限。在Erlang中常用的数据结构只有三种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原子（atom）：原子使用小写开头的标识符来表示。您可以把原子认为是一种字符串常量来看待，事实上它除了作为标识之外也没有额外的作用。 
&lt;li&gt;绑定（binding）：大写开头的标示符则为绑定，您可以近似地将其理解为“只能设置一次”的变量。一个绑定内部可以保存任何数据，如一个进程（Erlang的概念，并非指操作系统进程）的id，一个数字，或一个字符串。 
&lt;li&gt;元组（tuple）：顾名思义，“元组”即为“单元的组合”，单元即为“原子”，“绑定”以及其他“元组”，通过某种方式结合起来。如上述代码中{ping, Ping_PID}便是一个由原子“ping”和绑定“Ping_PID”组成。当然您也可以写成{do, {ping, Hello, World}, 7}这种嵌套的元组结构。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Erlang中的receive原语的作用是接受下一条消息，直到有可用消息时它才会执行下面的代码。Erlang使用了模式匹配（Pattern Matching）来表现接受到不同消息时的逻辑分支。如pong的实现：&lt;/p&gt;&lt;pre class="code"&gt;pong() -&amp;gt;
    receive
        finished -&amp;gt;
            io:format("Pong finished~n", []);
        {ping, Ping_PID} -&amp;gt;
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.
&lt;/pre&gt;
&lt;p&gt;在这段代码中，receive将会设法将消息与两种模式进行匹配：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原子finished，表示测试结束。&lt;/li&gt;
&lt;li&gt;元组{ping, Ping_PID}，表示一个元组，其中有两个单元，首先是ping原子，其次是Ping_PID绑定。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;在成功匹配了某个模式之后，其中的绑定也会随之被赋上特定的值。如匹配了{ping, Ping_PID}之后，Ping_PID便被赋值为ping这个Actor对象的标识符。而在接下来的逻辑中，便可以使用这些“绑定”中的值。由于元组的结构不会受到任何限制，因此开发人员可以使用它来表示任意的抽象数据类型——更确切地说，应该是“数据结构”吧。&lt;/p&gt;
&lt;h1&gt;Erlang的优势与缺陷&lt;/h1&gt;
&lt;p&gt;Erlang在消息执行方式上的优势在于灵活。Erlang是弱类型语言，在实现的时候可以任意调整消息的内容，或是模式的要求。在Erlang进行模式匹配时往往有种约定：使用“原子”来表示“做什么”，而使用“绑定”来获取操作所需要的“数据”，这种方式避免了冗余的cast和赋值，在使用的时候颇为灵活。然而，世上没有完美的事物，Erlang的消息执行方式也有缺陷，而且是较为明显的缺陷。&lt;/p&gt;
&lt;p&gt;首先，Erlang的数据抽象能力实在太弱。如果编写一个略显复杂的应用程序，您会发现程序里充斥着复杂的元组。您可能会疲于应对那些拥有7、8个单元（甚至跟多）的元组，一个一个数过来到底某个绑定匹配的是第几项，它的含义究竟是什么——一旦搞错，程序便会出错，而且想要调试都较为困难。因此，也有人戏称Erlang是一门“天生会损害人视力的语言”（令人惊讶的是，那篇文章居然搜不到了，我们只能&lt;a href="http://www.google.cn/search?hl=zh-CN&amp;amp;newwindow=1&amp;amp;q=erlang+视力&amp;amp;btnG=Google+搜索"&gt;从搜索引擎上看出点痕迹了&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;而我认为，这并不是Erlang语言中最大的问题，Erlang中最大的问题也是其“弱类型”特性。例如，现在有一个公用的Service Locator服务，任意类型的Actor都会像SL发送一个消息用于请求某个Service的位置，SL会在得到请求之后，向请求方发送一条消息表示应答。试想，如果SL的功能需要有所修改，作为回复的消息结构产生了变化，那么我们势必要修改每一个请求方中所匹配的模式。由于消息的发送方和接受方在实际上完全分离，没有基于任何协议，因此静态检查几乎无从做起。一旦遇到这种需要大规模的修改的情况，Erlang程序便很容易产生差错。因为一旦有所遗漏，系统便无法正常执行下去了。&lt;/p&gt;
&lt;p&gt;您对Erlang的感觉如何？这是一门会影响您编程思维的语言。老赵建议，即使您平时不会使用Erlang，也不妨简单接触一下这门语言。它的并发或容灾等特性给了我许多启示。相信您会有不少收获。&lt;/p&gt;
&lt;h1&gt;相关文章&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;适合C# Actor的消息执行方式（1）：Erlang中的模式匹配&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-2-embarrassing-c-sharp-actor.html"&gt;适合C# Actor的消息执行方式（2）：C# Actor的尴尬&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-3-nice-solution-with-little-use.html"&gt;适合C# Actor的消息执行方式（3）：中看不中用的解决方案&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-4-mid-stage-conclusion.html"&gt;适合C# Actor的消息执行方式（4）：阶段性总结&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-5-a-simple-web-crawler.html"&gt;适合C# Actor的消息执行方式（5）：一个简单的网络爬虫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.zhaojie.me/2009/08/message-execution-model-for-c-sharp-actor-6-covariance-and-contravariance.html"&gt;适合C# Actor的消息执行方式（6）：协变与逆变&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <comments>http://blog.zhaojie.me/2009/07/message-execution-model-for-c-sharp-actor-1-pattern-matching-in-erlang.html#comments</comments>
      <pubDate>Wed, 08 Jul 2009 16:18:00 GMT</pubDate>
      <lastBuildDate>Wed, 08 Jul 2009 16:18:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>天下无处不乒乓</title>
      <link>http://blog.zhaojie.me/2009/06/everything-ping-pong.html</link>
      <guid>http://blog.zhaojie.me/2009/06/everything-ping-pong.html</guid>
      <description>&lt;p&gt;在消息传递（Message Passing）领域，PingPong是最常见的测试之一。它的功能简单的有些无聊，一个Ping Actor和一个Pong Actor之间互相传递消息，你Ping过来我Pong过去。也正因为如此简单，PingPong的目标仅仅是测试纯粹的消息传递机制的效率。也正因为如此，各Actor模型往往都将其作为展示自己功能的第一个示例。老赵从互联网上收集了一些最为常见的，不同语言/平台下Actor模型实现PingPong的示例，可作“观赏”之用。&lt;/p&gt; &lt;p&gt;由于语言特性不同，有的Actor模型内置于语言之中的（如&lt;a href="http://erlang.org/"&gt;Erlang&lt;/a&gt;），其他大都由框架进行补充。有的Actor模型使用字符串作为消息，有的却通过语言功能而可以传递强类型的消息。简单的罗列各种实现，把这些实现从表面上进行简单的对比，也可以说颇有趣味。当然，这些实现虽然都使用了PingPong作为演示，但是其中细节还是略有不同的。例如有些会让Ping/Pong过程永远持续下去，而有些会让它们在进行一段时间过后就中止；有些会对命令进行识别，而有些对此并不在意。老赵认为这些区别无伤大雅，对此也未作任何修改。&lt;/p&gt; &lt;p&gt;本文会涉及以下Actor模型的实现（不少语言缺少语法着色，如果您有好用的HTML高亮方案请不吝指明）：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;&lt;a href="#erlang"&gt;Erlang&lt;/a&gt; &lt;small&gt;(&lt;a href="http://erlang.org/doc/getting_started/conc_prog.html#3.2"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;&lt;a href="#scala"&gt;Scala&lt;/a&gt; &lt;small&gt;(&lt;a href="http://www.scala-lang.org/node/242"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;Haskell &lt;small&gt;(&lt;a href="http://gist.github.com/111482"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;&lt;a href="#ruby"&gt;Ruby&lt;/a&gt; &lt;small&gt;(&lt;a href="http://gigix.thoughtworkers.org/2007/10/23/is-concurrent-ruby-better/"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;&lt;a href="#python"&gt;Python&lt;/a&gt; &lt;small&gt;(&lt;a href="http://members.verizon.net/olsongt/stackless/why_stackless.html#pingpong-stackless-py-stackless-ping-pong-example"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;&lt;a href="#axum-channel"&gt;Axum with Channel&lt;/a&gt; &lt;small&gt;(&lt;a href="http://codebetter.com/blogs/matthew.podwysocki/archive/2009/05/12/axum-introduction-and-ping-pong-example.aspx"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;Axum with Ordered Interaction Points &lt;small&gt;(&lt;a href="http://codebetter.com/blogs/matthew.podwysocki/archive/2009/06/04/axum-ping-pong-with-ordered-interaction-points.aspx"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;&lt;a href="#fsharp"&gt;F#&lt;/a&gt; &lt;small&gt;(&lt;a href="http://blogs.msdn.com/concurrently_speaking/archive/2009/05/12/actors-in-f.aspx"&gt;source&lt;/a&gt;)&lt;/small&gt;  &lt;li&gt;&lt;a href="#fsharp-actorlite"&gt;F# with ActorLite&lt;/a&gt; &lt;small&gt;(&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-3.html"&gt;source&lt;/a&gt;)&lt;/small&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在这些示例中，您会发现缺少了C#下的Actor——因为老赵打算下次单独开篇对此进行说明和介绍。:)&lt;/p&gt; &lt;h1 id="erlang"&gt;Erlang&lt;/h1&gt;&lt;pre class="code"&gt;-module(tut15).

-export([start/0, ping/2, pong/0]).

ping(0, Pong_PID) -&amp;gt;
    Pong_PID ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_PID) -&amp;gt;
    Pong_PID ! {ping, self()},
    receive
        pong -&amp;gt;
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_PID).

pong() -&amp;gt;
    receive
        finished -&amp;gt;
            io:format("Pong finished~n", []);
        {ping, Ping_PID} -&amp;gt;
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start() -&amp;gt;
    Pong_PID = spawn(tut15, pong, []),
    spawn(tut15, ping, [3, Pong_PID]).&lt;/pre&gt;
&lt;h1 id="scala"&gt;Scala&lt;/h1&gt;&lt;pre class="code"&gt;import scala.actors.Actor
import scala.actors.Actor._

class Ping(count: int, pong: Actor) extends Actor {
  def act() {
    var pingsLeft = count - 1
    pong ! Ping
    loop {
      react {
        case Pong =&amp;gt;
          if (pingsLeft % 1000 == 0)
            Console.println("Ping: pong")
          if (pingsLeft &amp;gt; 0) {
            pong ! Ping
            pingsLeft -= 1
          } else {
            Console.println("Ping: stop")
            pong ! Stop
            exit()
          }
      }
    }
  }
}

class Pong extends Actor {
  def act() {
    var pongCount = 0
    loop {
      react {
        case Ping =&amp;gt;
          if (pongCount % 1000 == 0)
            Console.println("Pong: ping "+pongCount)
          sender ! Pong
          pongCount = pongCount + 1
        case Stop =&amp;gt;
          Console.println("Pong: stop")
          exit()
      }
    }
  }
}

object pingpong extends Application {
  val pong = new Pong
  val ping = new Ping(100000, pong)
  ping.start
  pong.start
}&lt;/pre&gt;
&lt;h1 id="ruby"&gt;Ruby&lt;/h1&gt;&lt;pre class="code"&gt;require 'concurrent/actors'
include Concurrent::Actors

Message = Struct.new :ping, :pong

ping_thread = Actor.spawn do
  loop do
    Actor.receive do |f|
      f.when Message do |m|
        puts "PING" 
        sleep(1)
        m.pong &amp;lt;&amp;lt; Message.new(m.ping, m.pong)
      end
    end
  end
end

pong_thread = Actor.spawn do
  loop do
    Actor.receive do |f|
      f.when Message do |m|
        puts "PONG" 
        sleep(1)
        m.ping &amp;lt;&amp;lt; Message.new(m.ping, m.pong)
      end
    end
  end
end

ping_thread &amp;lt;&amp;lt; Message.new(ping_thread, pong_thread)
while(true)
  puts("WAITING...")
  sleep(5)
end&lt;/pre&gt;
&lt;h1 id="python"&gt;Python&lt;/h1&gt;&lt;pre class="code"&gt;#
# pingpong_stackless.py
#

import stackless

ping_channel = stackless.channel()
pong_channel = stackless.channel()

def ping():
    while ping_channel.receive(): #blocks here
        print "PING"
        pong_channel.send("from ping")

def pong():
    while pong_channel.receive():
        print "PONG"
        ping_channel.send("from pong")



stackless.tasklet(ping)()
stackless.tasklet(pong)()

# we need to 'prime' the game by sending a start message
# if not, both tasklets will block
stackless.tasklet(ping_channel.send)('startup')

stackless.run()&lt;/pre&gt;
&lt;h1 id="axum-channel"&gt;Axum with Channel&lt;/h1&gt;&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;using&lt;/span&gt;&lt;span style="color: #000000"&gt; System;
&lt;/span&gt;&lt;span style="color: #0000ff"&gt;using&lt;/span&gt;&lt;span style="color: #000000"&gt; System.Concurrency;
&lt;/span&gt;&lt;span style="color: #0000ff"&gt;using&lt;/span&gt;&lt;span style="color: #000000"&gt; System.Collections.Generic;
&lt;/span&gt;&lt;span style="color: #0000ff"&gt;using&lt;/span&gt;&lt;span style="color: #000000"&gt; Microsoft.Axum;
&lt;/span&gt;&lt;span style="color: #0000ff"&gt;using&lt;/span&gt;&lt;span style="color: #000000"&gt; System.Concurrency.Messaging;


&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;channel&lt;/span&gt;&lt;span style="color: #000000"&gt; PingPongStatus
{
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;input&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; HowMany;
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;output&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; Done;
    
    Start: { HowMany, Done &lt;/span&gt;&lt;span style="color: #000000"&gt;-&amp;gt;&lt;/span&gt;&lt;span style="color: #000000"&gt; End; }
}

&lt;/span&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;agent&lt;/span&gt;&lt;span style="color: #000000"&gt; Ping : &lt;/span&gt;&lt;span style="color: #0000ff"&gt;channel&lt;/span&gt;&lt;span style="color: #000000"&gt; PingPongStatus
{
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; Ping()
    {
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; How many are we supposed to do?&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt;&lt;span style="color: #000000"&gt; iters &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(PrimaryChannel::HowMany);
        
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Create pong and send how many to do&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt;&lt;span style="color: #000000"&gt; chan &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; Pong.CreateInNewDomain();      
        chan::HowMany &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; iters;
        
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Send pings and receive pongs&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;for&lt;/span&gt;&lt;span style="color: #000000"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; i &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #800080"&gt;0&lt;/span&gt;&lt;span style="color: #000000"&gt;; i &lt;/span&gt;&lt;span style="color: #000000"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000"&gt; iters; i&lt;/span&gt;&lt;span style="color: #000000"&gt;++&lt;/span&gt;&lt;span style="color: #000000"&gt;)
        {
            chan::Ping &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; Signal.Value;
            &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(chan::Pong);
            Console.WriteLine(&lt;/span&gt;&lt;span style="color: #800000"&gt;"&lt;/span&gt;&lt;span style="color: #800000"&gt;Ping received Pong&lt;/span&gt;&lt;span style="color: #800000"&gt;"&lt;/span&gt;&lt;span style="color: #000000"&gt;);
        }
        
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; How many did Pong process?&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; pongIters &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(chan::Done);

        PrimaryChannel::Done &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #800080"&gt;0&lt;/span&gt;&lt;span style="color: #000000"&gt;;
    }  
}&lt;/span&gt;

&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;channel&lt;/span&gt;&lt;span style="color: #000000"&gt; PingPong
{
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;input&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; HowMany;
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;output&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; Done;
    
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;input&lt;/span&gt;&lt;span style="color: #000000"&gt; Signal Ping;
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;output&lt;/span&gt;&lt;span style="color: #000000"&gt; Signal Pong;
}

&lt;/span&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;agent&lt;/span&gt;&lt;span style="color: #000000"&gt; Pong : &lt;/span&gt;&lt;span style="color: #0000ff"&gt;channel&lt;/span&gt;&lt;span style="color: #000000"&gt; PingPong
{
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; Pong()
    {
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Get how many we're supposed to do&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt;&lt;span style="color: #000000"&gt; iters &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(PrimaryChannel::HowMany);

        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;int&lt;/span&gt;&lt;span style="color: #000000"&gt; i &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #800080"&gt;0&lt;/span&gt;&lt;span style="color: #000000"&gt;;

        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Receive our ping and send back our pong&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;for&lt;/span&gt;&lt;span style="color: #000000"&gt; (; i &lt;/span&gt;&lt;span style="color: #000000"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000"&gt; iters; i&lt;/span&gt;&lt;span style="color: #000000"&gt;++&lt;/span&gt;&lt;span style="color: #000000"&gt;)
        {
            &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(PrimaryChannel::Ping);
            Console.WriteLine(&lt;/span&gt;&lt;span style="color: #800000"&gt;"&lt;/span&gt;&lt;span style="color: #800000"&gt;Pong received Ping&lt;/span&gt;&lt;span style="color: #800000"&gt;"&lt;/span&gt;&lt;span style="color: #000000"&gt;);
            PrimaryChannel::Pong &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; Signal.Value;    
        }
    
        PrimaryChannel::Done &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; i;
    }
}&lt;/span&gt;

&lt;/span&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;agent&lt;/span&gt;&lt;span style="color: #000000"&gt; Program : &lt;/span&gt;&lt;span style="color: #0000ff"&gt;channel&lt;/span&gt;&lt;span style="color: #000000"&gt; Application
{
    &lt;/span&gt;&lt;span style="color: #0000ff"&gt;public&lt;/span&gt;&lt;span style="color: #000000"&gt; Program()
    {
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Wait for our command args&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt;&lt;span style="color: #000000"&gt; cmdArgs &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(PrimaryChannel::CommandLine);
        
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Set the iterations to be some number&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt;&lt;span style="color: #000000"&gt; iters &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; &lt;/span&gt;&lt;span style="color: #800080"&gt;3&lt;/span&gt;&lt;span style="color: #000000"&gt;;
        
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Create instance of ping and send msg&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt;&lt;span style="color: #000000"&gt; chan &lt;/span&gt;&lt;span style="color: #000000"&gt;=&lt;/span&gt;&lt;span style="color: #000000"&gt; Ping.CreateInNewDomain();
        chan::HowMany &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; iters;
        
        &lt;/span&gt;&lt;span style="color: #008000"&gt;//&lt;/span&gt;&lt;span style="color: #008000"&gt; Receive how many done&lt;/span&gt;&lt;span style="color: #008000"&gt;
&lt;/span&gt;&lt;span style="color: #000000"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff"&gt;receive&lt;/span&gt;&lt;span style="color: #000000"&gt;(chan::Done);
        
        PrimaryChannel::Done &lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;--&lt;/span&gt;&lt;span style="color: #000000"&gt; Signal.Value;
    }
}&lt;/span&gt;
&lt;/pre&gt;
&lt;h1 id="fsharp"&gt;F#&lt;/h1&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;open &lt;/span&gt;System

&lt;span style="color: blue"&gt;type &lt;/span&gt;message = Finished | Msg &lt;span style="color: blue"&gt;of &lt;/span&gt;int * MailboxProcessor&amp;lt;message&amp;gt;

&lt;span style="color: blue"&gt;let &lt;/span&gt;ping iters (outbox : MailboxProcessor&amp;lt;message&amp;gt;) =
    MailboxProcessor.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
        let rec &lt;/span&gt;loop n = async { 
            &lt;span style="color: blue"&gt;if &lt;/span&gt;n &amp;gt; 0 &lt;span style="color: blue"&gt;then
                &lt;/span&gt;outbox.Post( Msg(n, inbox) )
                &lt;span style="color: blue"&gt;let! &lt;/span&gt;msg = inbox.Receive()
                Console.WriteLine(&lt;span style="color: maroon"&gt;"ping received pong"&lt;/span&gt;)
                &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop(n-1)
            &lt;span style="color: blue"&gt;else
                &lt;/span&gt;outbox.Post(Finished)
                Console.WriteLine(&lt;span style="color: maroon"&gt;"ping finished"&lt;/span&gt;)
                &lt;span style="color: blue"&gt;return &lt;/span&gt;()}
        loop iters)
            
&lt;span style="color: blue"&gt;let &lt;/span&gt;pong () =
    MailboxProcessor.Start(&lt;span style="color: blue"&gt;fun &lt;/span&gt;inbox &lt;span style="color: blue"&gt;-&amp;gt; 
        let rec &lt;/span&gt;loop () = async { 
            &lt;span style="color: blue"&gt;let! &lt;/span&gt;msg = inbox.Receive()
            &lt;span style="color: blue"&gt;match &lt;/span&gt;msg &lt;span style="color: blue"&gt;with
            &lt;/span&gt;| Finished &lt;span style="color: blue"&gt;-&amp;gt; 
                &lt;/span&gt;Console.WriteLine(&lt;span style="color: maroon"&gt;"pong finished"&lt;/span&gt;)
                &lt;span style="color: blue"&gt;return &lt;/span&gt;()
            | Msg(n, outbox) &lt;span style="color: blue"&gt;-&amp;gt; 
                &lt;/span&gt;Console.WriteLine(&lt;span style="color: maroon"&gt;"pong received ping"&lt;/span&gt;)
                outbox.Post(Msg(n, inbox)
                &lt;span style="color: blue"&gt;return! &lt;/span&gt;loop() }
                    
        loop())

&lt;span style="color: blue"&gt;let &lt;/span&gt;ponger = pong()
&lt;span style="color: blue"&gt;do &lt;/span&gt;(ping 10 ponger) |&amp;gt; ignore&lt;/pre&gt;
&lt;h1 id="fsharp-actorlite"&gt;F# with ActorLite&lt;/h1&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;#light

open &lt;/span&gt;System
&lt;span style="color: blue"&gt;open &lt;/span&gt;ActorLite

&lt;span style="color: blue"&gt;let &lt;/span&gt;(&amp;lt;=) (m:_ Actor) msg = m.Post msg

&lt;span style="color: blue"&gt;type &lt;/span&gt;message = string * message Actor

&lt;span style="color: blue"&gt;let &lt;/span&gt;pong = 
    { &lt;span style="color: blue"&gt;new &lt;/span&gt;message Actor() &lt;span style="color: blue"&gt;with
        override &lt;/span&gt;self.Receive(message) =
            &lt;span style="color: blue"&gt;let &lt;/span&gt;msg, ping = message
            Console.WriteLine(msg)
            ping &amp;lt;= (&lt;span style="color: maroon"&gt;"Pong"&lt;/span&gt;, self) }
            
&lt;span style="color: blue"&gt;let &lt;/span&gt;ping = 
    { &lt;span style="color: blue"&gt;new &lt;/span&gt;message Actor() &lt;span style="color: blue"&gt;with
        override &lt;/span&gt;self.Receive(message) =
            &lt;span style="color: blue"&gt;let &lt;/span&gt;msg, pong = message
            Console.WriteLine(msg)
            pong &amp;lt;= (&lt;span style="color: maroon"&gt;"Ping"&lt;/span&gt;, self) }

ping &amp;lt;= (&lt;span style="color: maroon"&gt;"Start"&lt;/span&gt;, pong)
Console.ReadLine |&amp;gt; ignore&lt;/pre&gt;</description>
      <comments>http://blog.zhaojie.me/2009/06/everything-ping-pong.html#comments</comments>
      <pubDate>Wed, 24 Jun 2009 04:46:00 GMT</pubDate>
      <lastBuildDate>Wed, 24 Jun 2009 04:46:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>ActorLite：一个轻量级Actor模型实现（下）</title>
      <link>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-3.html</link>
      <guid>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-3.html</guid>
      <description>&lt;p&gt;在&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-2.html"&gt;上一篇文章&lt;/a&gt;中，我们实现了一个简单的Actor模型。如果要构建一个Actor，便只是简单地继承Actor&amp;lt;T&amp;gt;类型并实现其Receive方法即可。在上次文章的末尾，我们使用C#演示了该Actor模型的使用。不过现在我们将尝试一下F#。&lt;/p&gt; &lt;h1&gt;C#使用Actor模型的缺陷&lt;/h1&gt; &lt;p&gt;在Erlang中，每个消息都使用模式匹配来限制其“结构”或“格式”，以此表达不同含义。C#类型系统的抽象能力远胜于Erlang，但是Erlang的“动态性”使得开发人员可以在程序中随意发送和接收任何类型，这种“自由”为Erlang带来了灵活。我们的Actor模型中，每个Actor对象都需要一种特定的消息格式，而这种消息格式承担了“表现Actor所有职责”的重任，但是一个Actor的职责是可能由任何数据组合而成。例如一段最简单的“聊天”程序，其Actor表示了一个“人”，用Erlang实现可能就会这么写：&lt;/p&gt;&lt;pre class="code"&gt;loop() -&amp;gt;
    &lt;font color="#0000ff"&gt;receive&lt;/font&gt;
        &lt;font color="#008000"&gt;% 系统要求发起聊天，于是向对方打招呼&lt;/font&gt;
        {start, Person} -&amp;gt;
            Person ! {self(), {greeting, &lt;font color="#800000"&gt;"你好"&lt;/font&gt;)},
            loop();

        &lt;font color="#008000"&gt;% 有人前来发起聊天，于是向对方说了点什么&lt;/font&gt;
        {Person, {greeting, Message}} -&amp;gt;
            Person ! {self(), {say, &lt;font color="#800000"&gt;"..."&lt;/font&gt;}},
            loop();

        &lt;font color="#008000"&gt;% 有人前来说话，于是拜拜&lt;/font&gt;
        {Person, {say, Message}} -&amp;gt;
            Person ! {self(), {bye, &lt;font color="#800000"&gt;"..."&lt;/font&gt;}},
            loop();

        ...
    &lt;font color="#0000ff"&gt;end.&lt;/font&gt;&lt;/pre&gt;
&lt;p&gt;不同的元组（tuple）配合不同的原子（atom）便表示了一条消息的“含义”，但是使用C#您又该怎样来表现这些“命令”呢？您可能会使用：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用object[]作为消息类型，并检查其元素。 
&lt;li&gt;使用object作为消息类型，并判断消息的具体类型。 
&lt;li&gt;使用枚举或字符串代表“命令”，配合一个参数集合。&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;第1种做法十分麻烦；第2种则需要“先定义，后使用”也颇为不易；而第3种做法，平心而论，如果有一个“分发类库”的支持就会比较理想——可能比这篇文章中的F#还要理想。老赵正在努力实现这一功能，因为C#的这个特性会影响到.NET平台下所有Actor模型（如&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html"&gt;第一篇&lt;/a&gt;文章中所提到的CCR或Retlang）的使用。&lt;/p&gt;
&lt;p&gt;而目前，我们先来看看F#是否可以略为缓解一下这方面的问题。&lt;/p&gt;
&lt;h1 id="fsharp"&gt;在F#中使用Actor模型&lt;/h1&gt;
&lt;p&gt;Erlang没有严谨的类型系统，其“消息类型”是完全动态的，因此非常灵活。那么F#又有什么“法宝”可以解决C#中所遇到的尴尬呢？在现在这个问题上，F#有三个领先于C#的关键：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;灵活的类型系统 
&lt;li&gt;强大的模式匹配 
&lt;li&gt;自由的语法&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;虽然F#也是强类型的编译型语言（这点和C#一致），但是F#的类型系统较C#灵活许多，例如在“聊天”这个示例中，我们就可以编写如下类型作为“消息”类型：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;Message = string
&lt;span style="color: blue"&gt;type &lt;/span&gt;ChatMsg = 
    | Start &lt;span style="color: blue"&gt;of &lt;/span&gt;Person
    | Greeting &lt;span style="color: blue"&gt;of &lt;/span&gt;Person * Message
    | Say &lt;span style="color: blue"&gt;of &lt;/span&gt;Person * Message
    | Bye &lt;span style="color: blue"&gt;of &lt;/span&gt;Person * Message&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;在这个定义中用到了F#类型系统中的三个特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;类型别名：即type Message = string。为一个已有的类型定义一个别名，可以得到更好的语义。与C#使用using定义别名不同的是，F#中的别名可以定义为全局性的，而不仅仅是“源代码”级别的别名。 
&lt;li&gt;Discriminated Unions：即type ChatMsg = …。Discriminated Unions可以为一个类型指定多个discriminator，每个discriminator由一个名称，以及另一种具体类型来表示。不同的discriminator的具体类型可以不同。 
&lt;li&gt;元组（Tuple）：即Person * Message。在F#中可以通过把现有类型按顺序进行任意组合来得到新的类型，这种类型便被称为“元组”。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;在Actor模型中，我们便组合了F#的三个特别特性，定义了消息的具体类型。而在使用时，我们便可以使用“模式匹配”对不同的“消息”——其实是CharMsg的不同discriminator进行不同地处理。于是具体的Actor类型Person，便可以使用如下定义：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;and &lt;/span&gt;Person(name: string) = 
    &lt;span style="color: blue"&gt;inherit &lt;/span&gt;ChatMsg Actor()
    
    &lt;span style="color: blue"&gt;let &lt;/span&gt;GetRandom = 
        &lt;span style="color: blue"&gt;let &lt;/span&gt;r = &lt;span style="color: blue"&gt;new &lt;/span&gt;Random(DateTime.Now.Millisecond)
        &lt;span style="color: blue"&gt;fun&lt;/span&gt;() &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;r.NextDouble()

    &lt;span style="color: blue"&gt;member &lt;/span&gt;self.Name = name
    
    &lt;span style="color: blue"&gt;override &lt;/span&gt;self.Receive(message) =
        &lt;span style="color: blue"&gt;match &lt;/span&gt;(message) &lt;span style="color: blue"&gt;with
&lt;/span&gt;&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;Person类的构造函数接受一个name作为参数，并将其放置到Name属性中。我们同时定义了GetRandom函数，它会在内部构造一个System.Random对象，并每次返回NextDouble方法的值（请注意，无论调用多少次GetRandom方法，永远使用了同一个Random对象，因为他是在定义GetRandom方法时创建的）。而在override的Receive方法中，我们使用“模式匹配”对message对象进行处理：&lt;/p&gt;&lt;pre class="code"&gt;        &lt;span style="color: green"&gt;// 系统要求发起聊天&lt;/span&gt;
        | Start(p) &lt;span style="color: blue"&gt;-&amp;gt; 
            &lt;/span&gt;Console.WriteLine(&lt;span style="color: maroon"&gt;"系统让{0}向{1}打招呼"&lt;/span&gt;, self.Name, p.Name)
            Greeting(self, &lt;span style="color: maroon"&gt;"Hi, 有空不？"&lt;/span&gt;) |&amp;gt; p.Post&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;请注意上述最后一行，原本我们使用p.Post(…)的调用方式，现在使用了“|&amp;gt;”符号代替。在F#中，x |&amp;gt; f便代表了f(x)，它的本意是可以把f(g(h(x)))这样冗余的调用方式转变为清晰的“消息发送”形式：x |&amp;gt; h |&amp;gt; g |&amp;gt; f。而“消息发送”也恰好是我们所需要的“感觉”。因此，我们在接下来的代码中也使用这样的方式：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: green"&gt;        // 打招呼&lt;/span&gt;
        | Greeting(p, msg) &lt;span style="color: blue"&gt;-&amp;gt;
            &lt;/span&gt;Console.WriteLine(&lt;span style="color: maroon"&gt;"{0}向{1}打招呼：{2}"&lt;/span&gt;, p.Name, self.Name, msg)
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(GetRandom() &amp;lt; 0.8) &lt;span style="color: blue"&gt;then
                &lt;/span&gt;Say(self, &lt;span style="color: maroon"&gt;"好，聊聊。"&lt;/span&gt;) |&amp;gt; p.Post
            &lt;span style="color: blue"&gt;else
                &lt;/span&gt;Bye(self, &lt;span style="color: maroon"&gt;"没空，bye！"&lt;/span&gt;) |&amp;gt; p.Post
        &lt;span style="color: green"&gt;// 进行聊天&lt;/span&gt;
        | Say(p, msg) &lt;span style="color: blue"&gt;-&amp;gt;
            &lt;/span&gt;Console.WriteLine(&lt;span style="color: maroon"&gt;"{0}向{1}说道：{2}"&lt;/span&gt;, p.Name, self.Name, msg)
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(GetRandom() &amp;lt; 0.8) &lt;span style="color: blue"&gt;then
                &lt;/span&gt;Say(self, &lt;span style="color: maroon"&gt;"继续聊。"&lt;/span&gt;) |&amp;gt; p.Post
            &lt;span style="color: blue"&gt;else
                &lt;/span&gt;Bye(self, &lt;span style="color: maroon"&gt;"聊不动了，bye！"&lt;/span&gt;) |&amp;gt; p.Post
        &lt;span style="color: green"&gt;// 结束&lt;/span&gt;
        | Bye(p, msg) &lt;span style="color: blue"&gt;-&amp;gt;
            &lt;/span&gt;Console.WriteLine(&lt;span style="color: maroon"&gt;"{0}向{1}再见：{2}"&lt;/span&gt;, p.Name, self.Name, msg)
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;至此，Person类型定义完毕。我们构造三个Person对象，让它们随意聊天：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;startChat() =
    &lt;span style="color: blue"&gt;let &lt;/span&gt;p1 = &lt;span style="color: blue"&gt;new &lt;/span&gt;Person(&lt;span style="color: maroon"&gt;"Tom"&lt;/span&gt;)
    &lt;span style="color: blue"&gt;let &lt;/span&gt;p2 = &lt;span style="color: blue"&gt;new &lt;/span&gt;Person(&lt;span style="color: maroon"&gt;"Jerry"&lt;/span&gt;)
    &lt;span style="color: blue"&gt;let &lt;/span&gt;p3 = &lt;span style="color: blue"&gt;new &lt;/span&gt;Person(&lt;span style="color: maroon"&gt;"老赵"&lt;/span&gt;)
    Start(p2) |&amp;gt; p1.Post
    Start(p3) |&amp;gt; p2.Post

startChat()
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;结果如下（内容会根据随机结果不同而有所改变）：&lt;/p&gt;&lt;pre class="code"&gt;系统让Tom向Jerry打招呼
系统让Jerry向老赵打招呼
Jerry向老赵打招呼：Hi, 有空不？
Tom向Jerry打招呼：Hi, 有空不？
Jerry向Tom说道：好，聊聊。
老赵向Jerry说道：好，聊聊。
Jerry向老赵说道：继续聊。
Tom向Jerry说道：继续聊。
Jerry向Tom说道：继续聊。
老赵向Jerry说道：继续聊。
Jerry向老赵说道：继续聊。
Tom向Jerry再见：聊不动了，bye！
老赵向Jerry说道：继续聊。
Jerry向老赵再见：聊不动了，bye！&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;h1&gt;使用Actor模型抓取网络数据&lt;/h1&gt;
&lt;p&gt;我们再来看一个略为“现实”一点的例子，需要多个Actor进行配合。首先，我们定义一个“抓取”数据用的Actor，它的唯一作用便是接受一个消息，并将抓取结果传回：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;type &lt;/span&gt;Crawler() =
    &lt;span style="color: blue"&gt;inherit &lt;/span&gt;((obj Actor) * string) Actor()

    &lt;span style="color: blue"&gt;override &lt;/span&gt;self.Receive(message) =
        &lt;span style="color: blue"&gt;let &lt;/span&gt;(monitor, url) = message
        &lt;span style="color: blue"&gt;let &lt;/span&gt;content = (&lt;span style="color: blue"&gt;new &lt;/span&gt;WebClient()).DownloadString(url)
        (url, content) |&amp;gt; monitor.Post
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;再使用“单件”方式直接定义一个monitor对象：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;monitor =
    { &lt;span style="color: blue"&gt;new &lt;/span&gt;obj Actor() &lt;span style="color: blue"&gt;with
        override &lt;/span&gt;self.Receive(message) =
            &lt;span style="color: blue"&gt;match &lt;/span&gt;message &lt;span style="color: blue"&gt;with
            &lt;/span&gt;&lt;span style="color: green"&gt;// crawling
            &lt;/span&gt;| :? string &lt;span style="color: blue"&gt;as &lt;/span&gt;url &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;(self, url) |&amp;gt; (&lt;span style="color: blue"&gt;new &lt;/span&gt;Crawler()).Post

            &lt;span style="color: green"&gt;// get crawled result
            &lt;/span&gt;| :? (string * string) &lt;span style="color: blue"&gt;as &lt;/span&gt;p &lt;span style="color: blue"&gt;-&amp;gt;
                let &lt;/span&gt;(url, content) = p
                Console.WriteLine(&lt;span style="color: maroon"&gt;"{0} =&amp;gt; {1}"&lt;/span&gt;, url, content.Length)

            &lt;span style="color: green"&gt;// unrecognized message
            &lt;/span&gt;| _ &lt;span style="color: blue"&gt;-&amp;gt; &lt;/span&gt;failwith &lt;span style="color: maroon"&gt;"Unrecognized message" &lt;/span&gt;}
&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;每次收到“抓取”消息时，monitor都会创建一个Crawler对象，并把url发送给它，并等待回复消息。而在使用时，只要把对象一个一个“发送”给monitor便可：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;let &lt;/span&gt;urls = [
    &lt;span style="color: maroon"&gt;"http://www.live.com"&lt;/span&gt;;
    &lt;span style="color: maroon"&gt;"http://www.baidu.com"&lt;/span&gt;;
    &lt;span style="color: maroon"&gt;"http://www.google.com"&lt;/span&gt;;
    &lt;span style="color: maroon"&gt;"http://www.cnblogs.com"&lt;/span&gt;;
    &lt;span style="color: maroon"&gt;"http://www.microsoft.com"&lt;/span&gt;]

List.iter monitor.Post urls&lt;/pre&gt;&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;
&lt;p&gt;运行结果如下：&lt;/p&gt;&lt;pre class="code"&gt;http://www.live.com =&amp;gt; 18035
http://www.google.com =&amp;gt; 6942
http://www.cnblogs.com =&amp;gt; 62688
http://www.microsoft.com =&amp;gt; 1020
http://www.baidu.com =&amp;gt; 3402&lt;/pre&gt;
&lt;h1&gt;性能分析&lt;/h1&gt;
&lt;p&gt;最后，我们再对这个Actor模型的性能作一点简单的分析。&lt;/p&gt;
&lt;p&gt;如果从“锁”的角度来说，这个Actor模型唯一的锁是在消息队列的访问上，这基本上就是唯一的瓶颈。如果把它替换为lock-free的队列，那么整个Actor模型就是完全的lock-free实现，其“调度”性能可谓良好。&lt;/p&gt;
&lt;p&gt;不过，从另一个角度来说，这个Actor模型的调度非常频繁，每次只执行一个消息。试想，如果执行一个消息只需要50毫秒，而进行一次调度就需要100毫秒，那么这个性能的瓶颈还是落在“调度”上。因此，如果我们需要进一步提高Actor模型的性能，则需要从Dispatcher.Execute方法上做文章，例如把每次执行一个消息修改为每次执行n个消息，或超过一个时间的阈值再进行下一次调度。减少调度，也是提高Actor模型性能的关键之一。&lt;/p&gt;
&lt;p&gt;此外，如果觉得.NET自带的线程池性能不高，或者说会受到程序其他部分的影响，那么也可以使用独立的线程池进行替换。&lt;/p&gt;
&lt;p&gt;自然，任何性能优化都不能只凭感觉下手，一切都要用数据说话，因此在优化时一定要先建立合适的Profile机制，保证每一步优化都是有效的。&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;源代码及示例下载：&lt;a title="http://code.msdn.microsoft.com/ActorLite" href="http://code.msdn.microsoft.com/ActorLite"&gt;http://code.msdn.microsoft.com/ActorLite&lt;/a&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-3.html#comments</comments>
      <pubDate>Sat, 16 May 2009 09:52:00 GMT</pubDate>
      <lastBuildDate>Sat, 16 May 2009 09:52:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>ActorLite：一个轻量级Actor模型实现（中）</title>
      <link>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-2.html</link>
      <guid>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-2.html</guid>
      <description>&lt;p&gt;从&lt;a href="http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html"&gt;上一篇文章&lt;/a&gt;的反响来看，似乎大家对于这一话题并没有太大兴趣。而这篇文章将会为大家带来一个简单但完整的Actor模型实现。此外，在下一篇文章中……可能会出现一些让您觉得有趣的东西。:)&lt;/p&gt; &lt;h1&gt;任务分配逻辑&lt;/h1&gt; &lt;p&gt;如上文所述，这次要实现的是一个非常简单的Actor模型，使用基于事件的分配方式，直接把任务交给.NET自带的线程池去使用。不过我们又该什么时候把一个Actor推入线程池的执行队列呢？这其实取决于我们执行Actor的两个“基本原则”：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;如果Actor的邮箱中包含消息，那么要尽早执行。  &lt;li&gt;对于单个Actor对象来说，它的消息是顺序执行的。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;因此，我们有两个“时机”可以把一个Actor交由线程池去执行：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;当Actor接收到一个消息（且该Actor处于“等待”状态）  &lt;li&gt;当Actor执行完一个消息（且Actor的邮箱中存在更多消息）&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;显然，在进行操作时需要小心处理并发造成的问题，因为一个“执行完”和多个“接受到”事件可能同时出现。如果操作不当，则容易出现各种错误的情况：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;某个Actor的邮箱未空，却已停止执行。  &lt;li&gt;同一个Actor的两个消息被并行地处理。  &lt;li&gt;Actor的邮箱已经没有消息，却被要求再次执行。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;至于并行控制的方式，就请关注下面的实现吧。&lt;/p&gt; &lt;h1&gt;简单的Actor模型实现&lt;/h1&gt; &lt;p&gt;Actor模型中最关键的莫过于Actor对象的实现。一个Actor的功能有如下三种：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;将消息放入邮箱  &lt;li&gt;接受并处理消息  &lt;li&gt;循环/退出循环&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;因此Actor抽象类对外的接口大致如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;IActor
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected abstract void &lt;/span&gt;Receive(T message);

    &lt;span style="color: blue"&gt;protected void &lt;/span&gt;Exit() { ... }

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Post(T message) { ... }
}
&lt;/pre&gt;
&lt;p&gt;三个方法的签名应该已经充分说明了各自的含义。不过IActor又是什么呢？请看它的定义：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal interface &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IActor
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;void &lt;/span&gt;Execute();

    &lt;span style="color: blue"&gt;bool &lt;/span&gt;Existed { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }

    &lt;span style="color: blue"&gt;int &lt;/span&gt;MessageCount { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }

    &lt;span style="color: #2b91af"&gt;ActorContext &lt;/span&gt;Context { &lt;span style="color: blue"&gt;get&lt;/span&gt;; }
}
&lt;/pre&gt;
&lt;p&gt;这是一个internal修饰的类型，这意味着它的访问级别被限制在程序集内部。IActor接口的作用是作为一个统一的类型，交给Dispatcher——也就是Actor模型的任务分发逻辑所使用的。IActor接口的前三个成员很容易从名称上理解其含义，那么ActorContext又是做什么用的呢？&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActorContext
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;ActorContext(&lt;span style="color: #2b91af"&gt;IActor &lt;/span&gt;actor)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Actor = actor;
    }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IActor &lt;/span&gt;Actor { &lt;span style="color: blue"&gt;get&lt;/span&gt;; &lt;span style="color: blue"&gt;private set&lt;/span&gt;; }

    ...
}

&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;IActor
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;protected &lt;/span&gt;Actor()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_context = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActorContext&lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActorContext &lt;/span&gt;m_context;
    &lt;span style="color: #2b91af"&gt;ActorContext IActor&lt;/span&gt;.Context
    {
        &lt;span style="color: blue"&gt;get
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_context;
        }
    }

    &lt;font color="#0000ff"&gt;...&lt;/font&gt;
}
&lt;/pre&gt;
&lt;p&gt;在多线程的环境中，进行一些同步控制是非常重要的事情。线程同步的常用手段是lock，不过如果要减小锁的粒度，那么势必会使用Interlocked类下的CAS等原子操作，而那些操作只能针对最基础的域变量，而不能针对经过封装的属性或方法等成员。ActorContext便包含了用于同步控制，以及其他直接表示Actor内部状态各种字段的对象。这样，我们便可以通过ActorContext对象来实现一个Lock-Free的链表或队列。您可以会说，那么为什么要用独立的ActorContext类型，而不直接把字段放置在统一的基类（例如ActorBase）中呢？这有两点原因，第一点是所谓的“统一控制”便于管理，而第二点才是更为关键的：后文会涉及到F#对这Actor模型的使用，只可惜F#在对待父类的internal成员时有一个bug，因此不得不把相关实现替换成接口（IActor）。不过这不是本文的主题，我们下次再讨论F#的问题。&lt;/p&gt;
&lt;p&gt;ActorContext目前只有一个字段——没错，只需要一个，这个字段便是表示状态的m_status。&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActorContext
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;public const int &lt;/span&gt;WAITING = 0;
    &lt;span style="color: blue"&gt;public const int &lt;/span&gt;EXECUTING = 1;
    &lt;span style="color: blue"&gt;public const int &lt;/span&gt;EXITED = 2;

    &lt;span style="color: blue"&gt;public int &lt;/span&gt;m_status;
}
&lt;/pre&gt;
&lt;p&gt;m_status字段的类型为int，而不是枚举，这是为了可以使用Interlocked中的CAS操作。而对这个状态的操作，也正好形成了我们同步操作过程中的“壁垒”。我们的每个Actor在任意时刻都处于三种状态之一：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;等待（Waiting）：邮箱为空，或刚执行完一个消息，正等待分配任务。 
&lt;li&gt;执行（Executing）：正在执行一个消息（确切地说，由于线程池的缘故，它也可能是还在队列中等待，不过从概念上理解，我们认为它“已经”执行了）。 
&lt;li&gt;退出（Exited）：已经退出，不会再执行任何消息。&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;显然，只有当m_status为WAITING时才能够为Actor分配运算资源（线程）以便执行，而分配好资源（将其推入.NET线程池）之后，它的状态就要变成EXECUTING。这恰好可以用一个原子操作形成我们需要的“壁垒”，可以让多个“请求”，“有且只有一个”成功，即“把Actor的执行任务塞入线程池”。如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dispatcher
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;ReadyToExecute(&lt;span style="color: #2b91af"&gt;IActor &lt;/span&gt;actor)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(actor.Existed) &lt;span style="color: blue"&gt;return&lt;/span&gt;;

        &lt;span style="color: blue"&gt;int &lt;/span&gt;status = &lt;span style="color: #2b91af"&gt;Interlocked&lt;/span&gt;.CompareExchange(
            &lt;span style="color: blue"&gt;ref &lt;/span&gt;actor.Context.m_status,
            &lt;span style="color: #2b91af"&gt;ActorContext&lt;/span&gt;.EXECUTING,
            &lt;span style="color: #2b91af"&gt;ActorContext&lt;/span&gt;.WAITING);

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(status == &lt;span style="color: #2b91af"&gt;ActorContext&lt;/span&gt;.WAITING)
        {
            &lt;span style="color: #2b91af"&gt;ThreadPool&lt;/span&gt;.QueueUserWorkItem(&lt;span style="color: blue"&gt;this&lt;/span&gt;.Execute, actor);
        }
    }

    ...
}
&lt;/pre&gt;
&lt;p&gt;CompareExchange方法返回这次原子操作前m_status的值，如果它为WAITING，那么这次操作（也仅有这次操作）成功地将m_status修改为EXECUTING。在这个情况下，Actor将会被放入线程池，将会由Execute方法来执行。从上述实现中我们可以发现，这个方法在多线程的情况下也能够正常工作。那么ReadyToExecute方法该在什么地方被调用呢？应该说是在任何“可能”让Actor开始执行的时候得到调用。按照文章开始的说法，其中一个情况便是“当Actor接收到一个消息时”：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;IActor
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Queue&lt;/span&gt;&amp;lt;T&amp;gt; m_messageQueue = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Queue&lt;/span&gt;&amp;lt;T&amp;gt;();

    ...

    &lt;span style="color: blue"&gt;public void &lt;/span&gt;Post(T message)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_exited) &lt;span style="color: blue"&gt;return&lt;/span&gt;;

        &lt;span style="color: blue"&gt;lock &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_messageQueue)
        {
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_messageQueue.Enqueue(message);
        }

        &lt;span style="color: #2b91af"&gt;Dispatcher&lt;/span&gt;.Instance.ReadyToExecute(&lt;span style="color: blue"&gt;this&lt;/span&gt;);
    }
}
&lt;/pre&gt;
&lt;p&gt;而另一个地方，自然是消息“执行完毕”，且Actor的邮箱中还拥有消息的时候，则再次为其分配运算资源。这便是Dispatcher.Execute方法的逻辑：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public abstract class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;T&amp;gt; : &lt;span style="color: #2b91af"&gt;IActor
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;bool &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IActor&lt;/span&gt;.Existed
    {
        &lt;span style="color: blue"&gt;get
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_exited;
        }
    }

    &lt;span style="color: blue"&gt;int &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IActor&lt;/span&gt;.MessageCount
    {
        &lt;span style="color: blue"&gt;get
        &lt;/span&gt;{
            &lt;span style="color: blue"&gt;return this&lt;/span&gt;.m_messageQueue.Count;
        }
    }

    &lt;span style="color: blue"&gt;void &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IActor&lt;/span&gt;.Execute()
    {
        T message;
        &lt;span style="color: blue"&gt;lock &lt;/span&gt;(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_messageQueue)
        {
            message = &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_messageQueue.Dequeue();
        }

        &lt;span style="color: blue"&gt;this&lt;/span&gt;.Receive(message);
    }

    &lt;span style="color: blue"&gt;private bool &lt;/span&gt;m_exited = &lt;span style="color: blue"&gt;false&lt;/span&gt;;

    &lt;span style="color: blue"&gt;protected void &lt;/span&gt;Exit()
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_exited = &lt;span style="color: blue"&gt;true&lt;/span&gt;;
    }

    ...
}

&lt;span style="color: blue"&gt;internal class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Dispatcher
&lt;/span&gt;{
    ...

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;Execute(&lt;span style="color: blue"&gt;object &lt;/span&gt;o)
    {
        &lt;span style="color: #2b91af"&gt;IActor &lt;/span&gt;actor = (&lt;span style="color: #2b91af"&gt;IActor&lt;/span&gt;)o;
        actor.Execute();
&lt;/pre&gt;
&lt;p&gt;当程序执行到此处时，actor的Execute方法已经从邮箱尾部获取了一条消息，并交由用户实现的Receive方法执行。同时，Actor的Exit方法也可能被调用，使它的Exited属性返回true。不过到目前为止，因为ActorContext.m_status一直保持为EXECUTING，因此这段时间中任意新消息所造成的ReadyToExecute方法的调用都不会为Actor再次分配运算资源。不过接下来，我们将会修改m_status，这可能会造成竞争。那么我们又该怎么处理呢？&lt;/p&gt;
&lt;p&gt;如果用户调用了Actor.Exit方法，那么它的Exited属性则会返回true，我们可以将m_status设为EXITED，这样Actor再也不会回到WAITING状态，也就避免了无谓的资源分配：&lt;/p&gt;&lt;pre class="code"&gt;         &lt;span style="color: blue"&gt;if &lt;/span&gt;(actor.Existed)
        {
            &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.VolatileWrite(
                &lt;span style="color: blue"&gt;ref &lt;/span&gt;actor.Context.m_status,
                &lt;span style="color: #2b91af"&gt;ActorContext&lt;/span&gt;.EXITED);
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{&lt;/pre&gt;
&lt;p&gt;如果Actor没有退出，那么它会被短暂地切换为WAITING状态。此后如果Actor的邮箱中存在剩余的消息，那么我们会再次调用ReadyToExecute方法“尝试”再次为Actor分配运算资源：&lt;/p&gt;&lt;pre class="code"&gt;            &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.VolatileWrite(
                &lt;span style="color: blue"&gt;ref &lt;/span&gt;actor.Context.m_status,
                &lt;span style="color: #2b91af"&gt;ActorContext&lt;/span&gt;.WAITING);

            &lt;span style="color: blue"&gt;if &lt;/span&gt;(actor.MessageCount &amp;gt; 0)
            {
                &lt;span style="color: blue"&gt;this&lt;/span&gt;.ReadyToExecute(actor);
            }
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;显然，在VolatileWrite和ReadyToExecute方法之间，可能会到来一条新的消息，因而再次引发一次并行地ReadyToExecute调用。不过根据我们之前的分析，这样的竞争并不会造成问题，因此在这方面我们可以完全放心。&lt;/p&gt;
&lt;p&gt;至此，我们已经完整地实现了一个简单的Actor模型，逻辑清晰，功能完整——而这一切，仅仅用了不到150行代码。不用怀疑，这的确是事实。&lt;/p&gt;
&lt;h1 id="usage"&gt;使用示例&lt;/h1&gt;
&lt;p&gt;Actor模型的关键在于消息传递形式（Message Passing Style）的工作方式，通信的唯一手段便是传递消息。在使用我们的Actor模型之前，我们需要继承Actor&amp;lt;T&amp;gt;类来构建一个真正的Actor类型。例如一个最简单的计数器：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Counter &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;Actor&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;int&lt;/span&gt;&amp;gt;
{
    &lt;span style="color: blue"&gt;private int &lt;/span&gt;m_value;

    &lt;span style="color: blue"&gt;public &lt;/span&gt;Counter() : &lt;span style="color: blue"&gt;this&lt;/span&gt;(0) { }

    &lt;span style="color: blue"&gt;public &lt;/span&gt;Counter(&lt;span style="color: blue"&gt;int &lt;/span&gt;initial)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_value = initial;
    }

    &lt;span style="color: blue"&gt;protected override void &lt;/span&gt;Receive(&lt;span style="color: blue"&gt;int &lt;/span&gt;message)
    {
        &lt;span style="color: blue"&gt;this&lt;/span&gt;.m_value += message;

        &lt;span style="color: blue"&gt;if &lt;/span&gt;(message == -1)
        {
            &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: blue"&gt;this&lt;/span&gt;.m_value);
            &lt;span style="color: blue"&gt;this&lt;/span&gt;.Exit();
        }
    }
}
&lt;/pre&gt;
&lt;p&gt;当计数器收到-1以外的数值时，便会累加到它的计数器上，否则便会打印出当前的值并退出。这里无需做任何同步方面的考虑，因为对于单个Actor来说，所有的消息都是依次处理，不会出现并发的情况。Counter的使用自然非常简单：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    &lt;span style="color: #2b91af"&gt;Counter &lt;/span&gt;counter = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Counter&lt;/span&gt;();
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 10000; i++)
    {
        counter.Post(i);
    }

    counter.Post(-1);

    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.ReadLine();
}
&lt;/pre&gt;
&lt;p&gt;不过您可能会问，这样的调用又有什么作用，又能实现什么呢？您现在可以去网上搜索一些Actor模型解决问题的示例，或者您可以等待下一篇文章中，我们使用F#来操作这个Actor模型。您会发现，配合F#的一些特性，这个Actor模型会变得更加实用，更为有趣。&lt;/p&gt;
&lt;p&gt;此外，在下一篇文章里我们也会对这个Actor模型进行简单的性能分析。如果您要把它用在生产环境中，那么可能还需要对它再进行一些细微地调整。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation-2.html#comments</comments>
      <pubDate>Thu, 14 May 2009 01:29:00 GMT</pubDate>
      <lastBuildDate>Thu, 14 May 2009 01:29:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/language/">语言编程</category>
      <title>ActorLite：一个轻量级Actor模型实现（上）</title>
      <link>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html</link>
      <guid>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html</guid>
      <description>&lt;h1&gt;Actor模型&lt;/h1&gt; &lt;p&gt;Actor模型为并行而生，具&lt;a href="http://en.wikipedia.org/wiki/Actor_model"&gt;Wikipedia中的描述&lt;/a&gt;，它原本是为大量独立的微型处理器所构建的高性能网络而设计的模型。而目前，单台机器也有了多个独立的计算单元，这就是为什么在并行程序愈演愈烈的今天，Actor模型又重新回到了人们的视线之中了。Actor模型的理念非常简单：天下万物皆为Actor，Actor之间通过发送消息进行通信。Actor模型的执行方式有两个特点：&lt;/p&gt; &lt;ol&gt; &lt;li&gt;每个Actor，单线程地依次执行发送给它的消息。  &lt;li&gt;不同的Actor可以同时执行它们的消息。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;对于第1点至今还有一些争论，例如Actor是否可以并行执行它的消息，Actor是否应该保证执行顺序与消息到达的一致（祥见Wikipedia的相关词条）。而第2点是毋庸置疑的，因此Actor模型天生就带有强大的并发特性。我们知道，系统中执行任务的最小单元是线程，数量一定程度上是有限的，而过多的线程会占用大量资源，也无法带来最好的运行效率，因此真正在同时运行的Actor就会少很多。不过，这并不影响我们从概念上去理解“同一时刻可能有成千上万个Actor正在运行”这个观点。在这里，“正在运行”的含义是“处于运行状态”。&lt;/p&gt; &lt;p&gt;Actor模型的使用无处不在，即使有些地方并没有明确说采用的Actor模型：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;Google提出的Map/Reduce分布式运算平台&lt;/li&gt; &lt;li&gt;C#，Java等语言中的lock互斥实现&lt;/li&gt; &lt;li&gt;传统Email信箱的实现&lt;/li&gt; &lt;li&gt;……&lt;/li&gt;&lt;/ul&gt; &lt;h1&gt;Actor模型的现有实现&lt;/h1&gt; &lt;p&gt;提到Actor模型的实现就不得不提Erlang。Erlang专以Actor模型为准则进行设计，它的每个Actor被称作是“进程（Process）”，而进程之间唯一的通信方式便是相互发送消息。一个进程要做的，其实只是以下三件事情：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;创建其他进程  &lt;li&gt;向其他进程发送消息  &lt;li&gt;接受并处理消息&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;例如《&lt;a href="http://www.amazon.com/Programming-Erlang-Software-Concurrent-World/dp/193435600X/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1242041286&amp;amp;sr=8-1"&gt;Programming Erlang&lt;/a&gt;》中的一段代码：&lt;/p&gt;&lt;pre class="code"&gt;loop() -&amp;gt;
    &lt;font color="#0000ff"&gt;receive&lt;/font&gt;
        {From, {store, Key, Value}} -&amp;gt;
            put(Key, {ok, Value}),
            From ! {kvs, true},
            loop();
        {From, {lookup, Key}} -&amp;gt;
            From ! {kvs, get(Key)},
            loop()
    &lt;font color="#0000ff"&gt;end.&lt;/font&gt;&lt;/pre&gt;
&lt;p&gt;在Erlang中，大写开头的标识表示“变量（variable）”，而小写开头的标识表示“原子（atom）”，而大括号及其内部以逗号分割的数据结构，则被称作是“元组（tuple）”。以上代码的作用为一个简单的“名字服务（naming service）”，当接受到{From, {store, Key, Value}}的消息时，则表示从From这个进程发来一个store请求，要求把Value与Key进行映射。而接受到{From, {lookup, Key}}消息时，则表示从From这个进程发来一个请求，要求返回Key所对应的内容。服务本身，也是通过向消息来源进程（即From）发送消息来进行回复的。&lt;/p&gt;
&lt;p&gt;从Erlang语言的设计并不复杂，其类型系统更加几乎可以用“简陋”来形容，这使得其抽象能力十分欠缺，唯一的复杂数据结构似乎只有“元组”一种而已——不过我们现在不谈其缺陷，谈其“优势”。Erlang语言设计的最大特点便是引入了“模式匹配（pattern matching）”，当且仅当受到的消息匹配了我们预设的结构（例如上面的{XXX, {store, YYY, ZZZ}}），则会进入相应的逻辑片断。其次便是其&lt;a href="http://blog.zhaojie.me/2009/03/tail-recursion-and-continuation.html"&gt;尾递归&lt;/a&gt;的特性，可见上面的代码中在loop方法的结尾再次调用了loop方法。&lt;/p&gt;
&lt;p&gt;如果说Erlang语言专为Actor模型而设计，那么&lt;a href="http://www.scala-lang.org/"&gt;Scala&lt;/a&gt;语言（学Java的朋友们都去学Scala吧，那才是发展方向）中内置的&lt;a href="http://www.scala-lang.org/node/242"&gt;Actor类库&lt;/a&gt;则是外部语言Actor模型实现的经典案例了：&lt;/p&gt;&lt;pre class="code"&gt;class Pong extends Actor {
  def act() {
    var pongCount = 0
    while (true) {
      receive {
        case Ping =&amp;gt;
          if (pongCount % 1000 == 0)
            Console.println("Pong: ping " + pongCount)
          sender ! Pong
          pongCount = pongCount + 1
        case Stop =&amp;gt;
          Console.println("Pong: stop")
          exit()
      }
    }
  }
}
&lt;/pre&gt;
&lt;p&gt;Pong类继承了Actor模型，并覆盖其act方法。由于没有Erlang的尾递归特性，Scala Actor使用一个while (true)进行不断的循环。获取到消息之后，将会使用case语句对消息进行判断，并执行相应逻辑。Scala的Actor类库充分利用了Scala的语法特性，让Actor模型好像是Scala内置功能一样，非常漂亮。&lt;/p&gt;
&lt;p&gt;此外，其他较为著名的Actor模型实现还有&lt;a href="http://iolanguage.com/"&gt;Io Language&lt;/a&gt;、&lt;a href="http://code.google.com/p/jetlang/"&gt;Jetlang&lt;/a&gt;、以及.NET平台下的&lt;a href="http://msdn.microsoft.com/en-us/library/bb648752.aspx"&gt;MS CCR&lt;/a&gt;和&lt;a href="http://code.google.com/p/retlang/"&gt;Retlang&lt;/a&gt;。后文中我们还会简单提到.NET下Actor Model实现，其他内容就需要感兴趣的朋友们自行挖掘了。&lt;/p&gt;
&lt;h1&gt;Actor模型中的任务调度&lt;/h1&gt;
&lt;p&gt;Actor模型的任务调度方式分为“基于线程（thread-based）的调度”以及“基于事件（event-based）的调度”两种。&lt;/p&gt;
&lt;p&gt;基于线程的调度为每个Actor分配一个线程，在接受一个消息（如在Scala Actor中使用receive）时，如果当前Actor的“邮箱（mail box）”为空，则会阻塞当前线程直到获得消息为止。基于线程的调度实现起来较为简单，例如在.NET中可以通过Monitor.Wait/Pulse来轻松实现这样的生产/消费逻辑。不过基于线程的调度缺点也是非常明显的，由于线程数量受到操作系统的限制，把线程和Actor捆绑起来势必影响到系统中可以同时的Actor数量。而线程数量一多也会影响到系统资源占用以及调度，而在某些情况下大部分的Actor会处于空闲状态，而大量阻塞线程既是系统的负担，也是资源的浪费。因此基于线程的调度是一个拥有重大缺陷的实现，现有的Actor Model大都不会采取这种方式。&lt;/p&gt;
&lt;p&gt;于是另一种Actor模型的任务调度方式便是基于事件的调度。“事件”在这里可以简单理解为“消息到达”事件，而此时才会为Actor的任务分配线程并执行。很容易理解，我们现在便可以使用少量的线程来执行大量Actor产生的任务，既保证了运算资源的充分占用，也不会让系统在同时进行的太多任务中“疲惫不堪”，这样系统便可以得到很好的伸缩性。在Scala Actor中也可以选择使用“react”而不是“recive”方法来使用基于事件的方式来执行任务。&lt;/p&gt;
&lt;p&gt;现有的Actor Model一般都会使用基于事件的调度方式。不过某些实现，如MS CCR、Retlang、Jetlang等类库还需要客户指定资源分配方式，显式地指定Actor与资源池（即线程池）之间的对应关系。而如Erlang或Scala则隐藏了这方面的分配逻辑，由系统整体进行统一管理。前者与后者相比，由于进行了更多的人工干涉，其资源分配可以更加合理，执行效率也会更高——不过其缺点也很明显：会由此带来额外的复杂度。&lt;/p&gt;
&lt;p&gt;我们即将实现的简单Actor Model类库，也将使用了基于事件的调度方式。同样为了简化资源分配的过程，我们将直接使用.NET自带的线程池来运行任务。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/05/a-simple-actor-model-implementation.html#comments</comments>
      <pubDate>Mon, 11 May 2009 12:10:00 GMT</pubDate>
      <lastBuildDate>Mon, 11 May 2009 12:10:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/essential/">重中之重</category>
      <category domain="http://blog.zhaojie.me/dotnet/">.Net框架</category>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/practice/">实践优化</category>
      <title>计算机体系结构与程序性能</title>
      <link>http://blog.zhaojie.me/2009/01/system-architecture-and-program-performance.html</link>
      <guid>http://blog.zhaojie.me/2009/01/system-architecture-and-program-performance.html</guid>
      <description>&lt;p&gt;文章原来的题目是《计算机基础对.NET程序员是否重要》，再我看来，这是一句废话。&lt;/p&gt; &lt;p&gt;当然重要。可是好像有些朋友对于这点很疑惑。最近我又收到一封邮件，一个朋友问我说，他在大学里学的那些课程，似乎都无法对工作有所帮助——当然，这是他目前对于工作的观察，其中大部分是他通过博客园的“管中窥豹”得来的结果：&lt;/p&gt; &lt;ul&gt; &lt;li&gt;好多讲ASP.NET的文章啊，控件真好用，做网站很方便。  &lt;li&gt;ORM好像对开发很有帮助，我们也来LINQ to SQL，NHibernate一下……网站不就是CRUD吗？  &lt;li&gt;JavaScript框架XXX的效果好炫，网站越来越漂亮了。  &lt;li&gt;好像进入并行处理时代了？呀，有了微软的并行库，加上AsParallel方法就能变快了。  &lt;li&gt;……&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;（以上内容略有艺术夸张，如有雷同，绝非巧合）&lt;/p&gt; &lt;p&gt;是啊，看看大学里的那些课程：数据结构、算法、操作系统、网络、计算机组成原理、编译原理……工作中哪里用上了？于是乎，那位朋友说，他看不少同学平时不上课，也能做做几个网站赚点小钱，生活过的十分滋润。而自己却越学越迷茫，不知应该“转行”去“学做网站”，还是继续“好好学习，天天向上”。我给他的建议是：在大学就好好上课，珍惜学校里的宝贵时光，以免工作时候后悔；如果觉得老师课上得不好，那么就自己看教材，用心去看（不过老师上的好还是要靠自己课后钻研）；如果嫌教材不好，那么去买点好教材，现在似乎各种“美国名校教材”都能找到，然后埋头学习便是。&lt;/p&gt; &lt;p&gt;企业抱怨毕业生不好，不是因为他们学了大学里没有用的东西，而是因为学好的人实在太少了。“大学的东西没有用”，大部分原因是没有学好。&lt;/p&gt; &lt;p&gt;想起自己以前也写过类似的文章，叫做《&lt;a href="http://blog.zhaojie.me/2007/10/how-to-learn.html"&gt;我们到底该怎么学技术？如何成为一个优秀的技术人员？&lt;/a&gt;》。老赵自己又看了一遍，发现有朋友对文章里的这么一句话有不同看法：“如果您不了解计算机体系结构，又如何能在Multi-CPU（Multi-Core）时代写出真正高效的应用程序呢？”。他认为这个“如果”（当然也包括文章里的其他“如果”）并不成立。所以老赵现在不谈“数据结构与算法如何有助于改善编程思维有什么改善”，或是“操作系统中线程调度、内存分页机制对于开发大型应用程序的参考价值”等“虚无缥缈”之物。在这篇文章里，我想通过两个直接的例子，来说明了解计算机体系结构对于提高程序性能有什么样的作用。&lt;/p&gt; &lt;h1&gt;Locality&lt;/h1&gt; &lt;p&gt;在描述何为Locality之前，我们先来看一个例子。例如，我们现在有一个二维数组：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;Main(&lt;span style="color: blue"&gt;string&lt;/span&gt;[] args)
{
    &lt;span style="color: blue"&gt;int &lt;/span&gt;n = 1 &amp;lt;&amp;lt; 10;
    &lt;span style="color: blue"&gt;int&lt;/span&gt;[,] array = &lt;span style="color: blue"&gt;new int&lt;/span&gt;[n, n];

    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;x = 0; x &amp;lt; n; x++)
    {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;y = 0; y &amp;lt; n; y++)
        {
            array[x, y] = x;
        }
    }

    ...
}
&lt;/pre&gt;
&lt;p&gt;我们要对这个1024 * 1024的二位数组中所有元素求和。那么我们会怎么写呢？先随手写一把：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static int &lt;/span&gt;SumA(&lt;span style="color: blue"&gt;int&lt;/span&gt;[,] array, &lt;span style="color: blue"&gt;int &lt;/span&gt;n)
{
    &lt;span style="color: blue"&gt;int &lt;/span&gt;sum = 0;
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;y = 0; y &amp;lt; n; y++)
    {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;x = 0; x &amp;lt; n; x++)
        {
            sum += array[x, y];
        }
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;sum;
}
&lt;/pre&gt;
&lt;p&gt;一个二重循环，遍历二维数组中每一个元素，相加，这也太容易了吧？是啊，不过我们还是可以“换种写法”的：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static int &lt;/span&gt;SumB(&lt;span style="color: blue"&gt;int&lt;/span&gt;[,] array, &lt;span style="color: blue"&gt;int &lt;/span&gt;n)
{
    &lt;span style="color: blue"&gt;int &lt;/span&gt;sum = 0;
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;x = 0; x &amp;lt; n; x++)
    {
        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;y = 0; y &amp;lt; n; y++)
        {
            sum += array[x, y];
        }
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;sum;
}
&lt;/pre&gt;
&lt;p&gt;仔细看看，有没有发现区别？没错，只是内层循环和外层循环的位置换了一下。这么做的意义何在？测试一下便知：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static void &lt;/span&gt;TestLocality(&lt;span style="color: blue"&gt;int&lt;/span&gt;[,] array, &lt;span style="color: blue"&gt;int &lt;/span&gt;n)
{
    &lt;span style="color: #2b91af"&gt;Stopwatch &lt;/span&gt;watch1 = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&lt;/span&gt;();
    watch1.Start();
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 100; i++) SumA(array, n);
    watch1.Stop();
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"SumA: " &lt;/span&gt;+ watch1.Elapsed);

    &lt;span style="color: #2b91af"&gt;Stopwatch &lt;/span&gt;watch2 = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Stopwatch&lt;/span&gt;();
    watch2.Start();
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; 100; i++) SumB(array, n);
    watch2.Stop();
    &lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;"SumB: " &lt;/span&gt;+ watch2.Elapsed);
}&lt;/pre&gt;
&lt;p&gt;我们把两种加法各执行100次，看看结果：&lt;/p&gt;&lt;pre class="code"&gt;SumA: 00:00:04.8116776
SumB: 00:00:00.8342202&lt;/pre&gt;
&lt;p&gt;第二种做法性能是第一种做法的将近5倍。这就是Locality的威力。&lt;/p&gt;
&lt;p&gt;Locality（局部性，不知道该不该这么翻译），通俗地说，就是通过利用“缓存”来提高程序运行效率。缓存是计算机中无所不在的概念，这里先借用《&lt;a href="http://www.amazon.com/Computer-Systems-Programmers-Randal-Bryant/dp/013034074X"&gt;Computer Systems: A Programmer's Perspective&lt;/a&gt;》（下文称为CSAPP）中的一幅图来简单说明一下：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/Learn-Computer-Architecture-For-Performance/1.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/Learn-Computer-Architecture-For-Performance/1.png" width="400"&gt;&lt;/a&gt;
&lt;p&gt;金字塔的每一层皆是“存储设备”，越靠近顶端则越速度越快，当然也越为昂贵。其中最快的当属寄存器，每个寄存器大小为一个字长（请问这是多大？），数量极其有限；L3则就是我们俗称的“内存”，大小……就取决于我们的荷包了；那么L1和L2是什么，大小又分别是多少呢？&lt;a href="http://en.wikipedia.org/wiki/L1-Cache"&gt;L1 Cache&lt;/a&gt;（又称Internal Cache），顾名思义它为CPU“内置”的缓存，速度次于寄存器，但还是远远高于内存。&lt;a href="http://en.wikipedia.org/wiki/L2_Cache"&gt;L2 Cache&lt;/a&gt;原本处于主板之上（因此又称External Cache），它比L1 Cache慢，速度也远高于内存，所以它做为CPU和Chips之间的缓冲——不过如今的CPU都已经“自带”L2 Cache，主板上自然就没有了。至于L1 Cache和L2 Cache的大小，您可以使用&lt;a href="http://www.cpuid.com/cpuz.php"&gt;CPU-Z&lt;/a&gt;看看您机器CPU中Cache的情况如何。这是老赵的工作机：&lt;/p&gt;
&lt;a href="http://img.zhaojie.me/blog/Learn-Computer-Architecture-For-Performance/2.png" target="_blank"&gt;&lt;img src="http://img.zhaojie.me/blog/Learn-Computer-Architecture-For-Performance/2.png" width="400"&gt;&lt;/a&gt;
&lt;p&gt;老赵的机器是双核，各有一个L1 Cache，分为L1 D-Cache（数据缓存）和L1 I-Cache（指令缓存），各32KB大小；两个核共用一个3MB大小的L2 Cache。Cache越大，CPU性能越高，这点毋庸置疑。&lt;/p&gt;
&lt;p&gt;L1 Cache和L2 Cache的描述中都有“64-byte line size”字样，表示Cache的Line长为64字节。Line为Cache每次向下级存储设备读取数据的大小。例如，程序在运行时寄存器会向L1请求内存中某个地址的数据（可能是4字节），如果L1中没有这个地址的值，则会向L2中读取包含该地址的一整个Line的数据——也就是64字节，但是并不保证请求的4字节在这64字节的头部或尾部，CPU自有其对齐机制；如果L2没有这个地址的数据，则会向操作系统进行请求，同样是一个Line，64字节。&lt;/p&gt;
&lt;p&gt;这就是Locality的关键。在系统中，一个好的Locality表现为，在读取某个地址之后的某个再次读取这个地址或者其附近的地址。由于在读取某个地址的数据之后，缓存中同样保留着附近地址的数据，因此如果请求临近的数据则速度就会非常快（L1 Cache无比迅速）。如果Locality很差，那么就会发现L1 Cache的失效率（Miss Ratio），甚至L2 Cache的失效率都会很高。如果CPU所需要的大量数据都要到L2 Cache甚至更为低效的内存中去读取（试想可能该次内存读取还需要从硬盘交换页上获得），那么性能不变差才令人奇怪。&lt;/p&gt;
&lt;p&gt;说了不少“理论”，那么我们来看看上面的例子中为什么第一种方法会远远慢于第二种算法。我们的二维数组每个元素的下标(x, y)如下所示：&lt;/p&gt;
&lt;table style="margin-bottom: 10px; text-align: center" cellspacing="0" cellpadding="2" border="1"&gt;
&lt;tbody&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;0, 0&lt;/td&gt;
&lt;td style="width: 60px"&gt;0, 1&lt;/td&gt;
&lt;td style="width: 60px"&gt;0, 2&lt;/td&gt;
&lt;td style="width: 60px"&gt;…&lt;/td&gt;
&lt;td style="width: 60px"&gt;…&lt;/td&gt;
&lt;td style="width: 60px"&gt;0, 1021&lt;/td&gt;
&lt;td style="width: 60px"&gt;0, 1022&lt;/td&gt;
&lt;td style="width: 60px"&gt;0, 1023&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;1, 0&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;2, 0&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;…&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;…&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;1021, 0&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;1022, 0&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style="width: 60px"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr style="height: 60px"&gt;
&lt;td style="width: 60px"&gt;1023, 0&lt;/td&gt;
&lt;td style="width: 60px"&gt;1023, 1&lt;/td&gt;
&lt;td style="width: 60px"&gt;1023, 2&lt;/td&gt;
&lt;td style="width: 60px"&gt;…&lt;/td&gt;
&lt;td style="width: 60px"&gt;…&lt;/td&gt;
&lt;td style="width: 60px"&gt;1023, 1021&lt;/td&gt;
&lt;td style="width: 60px"&gt;1023, 1022&lt;/td&gt;
&lt;td style="width: 60px"&gt;1023, 1023&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;在内存中，每个元素的地址按照以下顺序次序排布：&lt;/p&gt;
&lt;p&gt;(0, 0), (0, 1), (0, 2), …, (0, 1022), (0, 1023), (1, 0), (1, 1), …, (1023, 0), (1023, 1), …, (1023, 1022), (1023, 1023)&lt;/p&gt;
&lt;p&gt;假如按照SumA方法中的读取顺序，L1 Cache中的状况可能就会是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取(0, 0)位置数据 =&amp;gt; Cache Miss =&amp;gt; L1向L2获取(0, 0), (0, 1), …, (0, 14), (0, 15)共64B数据 =&amp;gt; 返回(0, 0)位置数据 
&lt;li&gt;读取(1, 0)位置数据 =&amp;gt; Cache Miss =&amp;gt; L1向L2获取(1, 0), (1, 1), …, (1, 14), (1, 15)共64B数据 =&amp;gt; 返回(1, 0)位置数据 
&lt;li&gt;…… 
&lt;li&gt;读取(1023, 0)位置数据 =&amp;gt; Cache Miss =&amp;gt; L1向L2获取(1023, 0), (1023, 1), …, (1023, 14), (1023, 15)共64B数据 =&amp;gt; 返回(1023, 0)位置数据 
&lt;li&gt;读取(0, 1)位置数据 =&amp;gt; Cache Miss（因为L1大小有限，读取(0, 0)时放入L1的64B数据已经被其他数据替换） =&amp;gt; L1向L2获取(0, 0), (0, 1), …, (0, 14), (0, 15)共64B数据 =&amp;gt; 返回(0, 1)位置数据 
&lt;li&gt;读取(1, 1)位置数据 =&amp;gt; Cache Miss（理由同上） =&amp;gt; L1向L2获取(1, 0), (1, 1), …, (1, 14), (1, 15)共64B数据 =&amp;gt; 返回(1, 1)位置数据 
&lt;li&gt;……&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;而按照在SumB方法中的读取顺序，L1 Cache中的状况可能就会是： &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取(0, 0)位置数据 =&amp;gt; Cache Miss =&amp;gt; L1向L2获取(0, 0), (0, 1), …, (0, 14), (0, 15)共64B数据 =&amp;gt; 返回(0, 0)位置数据 
&lt;li&gt;读取(0, 1)位置数据 =&amp;gt; Cache Hit =&amp;gt; 直接返回(0, 1)位置数据 
&lt;li&gt;读取(0, 2)位置数据 =&amp;gt; Cache Hit =&amp;gt; 直接返回(0, 2)位置数据 
&lt;li&gt;…… 
&lt;li&gt;读取(1, 0)位置数据 =&amp;gt; Cache Miss =&amp;gt; L1向L2获取(1, 0), (1, 1), …, (1, 14), (1, 15)共64B数据 =&amp;gt; 返回(1, 0)位置数据 
&lt;li&gt;读取(1, 1)位置数据 =&amp;gt; Cache Hit =&amp;gt; 直接返回返回(1, 1)位置数据 
&lt;li&gt;……&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;两种做法立分高下。&lt;/p&gt;
&lt;p&gt;其实Locality这一特性在很多系统或应用中都有体现，例如磁盘文件的顺序读取就远比随机读取要快。再举个“时髦”点的东西，Google的Map Reduce论文中就使用了一节来提到Locality——调度器往往选择GFS中文件所在的机器作为Map Worker，这样可以通过读取本地文件尽可能减少数据在网络中的传输，从而大大提高效率。&lt;/p&gt;
&lt;h1&gt;False Sharing&lt;/h1&gt;
&lt;p&gt;尽可能读取接近的数据可以通过加强Locality来提高效率，但是在目前的多核甚至多CPU的环境下，操作两个“位置接近”数据可能反而会坏事。&lt;/p&gt;
&lt;p&gt;再看一个例子。运行刚才的程序时您应该会发现CPU只用了50%左右，这是因为我们单线程的程序只能在一个核上运行。现在已经进入了并行时代，我们的程序也要与时俱进。微软推出了并行库让并行操作变得异常容易，那么我们就利用并行库来进行刚才二维数组所有元素求和。如下：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static int &lt;/span&gt;ParallelSumA(&lt;span style="color: blue"&gt;int&lt;/span&gt;[,] array, &lt;span style="color: blue"&gt;int &lt;/span&gt;n)
{
    &lt;span style="color: blue"&gt;int &lt;/span&gt;processorCount = &lt;span style="color: #2b91af"&gt;Environment&lt;/span&gt;.ProcessorCount;
    &lt;span style="color: blue"&gt;int&lt;/span&gt;[] result = &lt;span style="color: blue"&gt;new int&lt;/span&gt;[processorCount];

    &lt;span style="color: #2b91af"&gt;Parallel&lt;/span&gt;.For(0, processorCount, (part) =&amp;gt;
    {
        &lt;span style="color: blue"&gt;int &lt;/span&gt;minInclusive = part * n / processorCount;
        &lt;span style="color: blue"&gt;int &lt;/span&gt;maxExclusive = minInclusive + n / processorCount;

        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;x = minInclusive; x &amp;lt; maxExclusive; x++)
        {
            &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;y = 0; y &amp;lt; n; y++)
            {
                result[part] += array[x, y];
            }
        }
    });

    &lt;span style="color: blue"&gt;int &lt;/span&gt;sum = 0;
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; result.Length; i++)
    {
        sum += result[i];
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;sum;
}
&lt;/pre&gt;
&lt;p&gt;我们首先获取系统中的处理器数目（processorCount），然后根据这个数据对二维数组进行分块，每个线程负责其中一块的计算，并将其保存到一个中间值中（result数组），最后再把所有的中间值累加即可。这种做法是并行计算中常用的模式，有了并行库的帮助，代码可以变得非常简单，直观。不过还是要用数据说话，还是100次求和运算，消耗时间为：&lt;/p&gt;&lt;pre class="code"&gt;00:00:01.8105218&lt;/pre&gt;
&lt;p&gt;怎么所花时间反而比单线程要增加了！这固然有线程调度的开销在里面，但是问题的关键还是程序的写法有问题，这种写法发生了&lt;a href="http://en.wikipedia.org/wiki/False_sharing" title="False Sharing"&gt;False Sharing&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;False Sharing（错误共享）意为错误地共享了本不该共享的数据。由于每个核的L1缓存互相独立，因此CPU必须有一种机制，能够确保一个核在向它的L1 Cache中写入一个值之后，其他核内L1 Cache中包含这个数据的整个Line就会过期。这意味着其他核在读取地址相同，或者是接近的数据时会遇到L1 Cache Miss。CPU的这种同步机制就是&lt;a href="http://en.wikipedia.org/wiki/MESI"&gt;MESI协议&lt;/a&gt;。那么我们来分析一下上面的代码到底如何造成了False Sharing。&lt;/p&gt;
&lt;p&gt;上面的并行代码会将二维数组分割为独立的几块数据，并且将每一块数据之和存入result数组中。result数组很小，每次都会被完整地读入每个核的L1 Cache内，修改其中任何一个元素都会导致所有核内的result数据过期。于是，两个核在计算时可能就会发生如下情况（以下用CL0和CL1来代表两个核的L1缓存）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CL0读取result[0]的值 =&amp;gt; Cache Miss =&amp;gt; result数组被加载到CL0中 =&amp;gt; 修改CL0中result[0] 
&lt;li&gt;CL1读取result[1]的值 =&amp;gt; Cache Miss =&amp;gt; result数组被加载到CL1中 =&amp;gt; 修改CL1中result[1] 
&lt;li&gt;CL0读取result[0]的值 =&amp;gt; 由于刚才CL1修改了result[1]，导致整条Line失效，于是Cache Miss =&amp;gt; result数组被加载到CL0中 =&amp;gt; 修改CL0中result[0] 
&lt;li&gt;CL1读取result[1]的值 =&amp;gt; 由于刚才CL0修改了result[0]，导致整条Line失效，于是Cache Miss =&amp;gt; result数组被加载到CL1中 =&amp;gt; 修改CL0中result[1] 
&lt;li&gt;……&lt;/li&gt;&lt;/ol&gt;
&lt;p&gt;从此，两个核结下了不解之缘，他俩将会纠缠大部分时间，直到整个任务结束。当然，在上面的问题中，消除False Sharing非常容易：&lt;/p&gt;&lt;pre class="code"&gt;&lt;span style="color: blue"&gt;static int &lt;/span&gt;ParallelSumB(&lt;span style="color: blue"&gt;int&lt;/span&gt;[,] array, &lt;span style="color: blue"&gt;int &lt;/span&gt;n)
{
    &lt;span style="color: blue"&gt;int &lt;/span&gt;processorCount = &lt;span style="color: #2b91af"&gt;Environment&lt;/span&gt;.ProcessorCount;
    &lt;span style="color: blue"&gt;int&lt;/span&gt;[] result = &lt;span style="color: blue"&gt;new int&lt;/span&gt;[processorCount];

    &lt;span style="color: #2b91af"&gt;Parallel&lt;/span&gt;.For(0, processorCount, (part) =&amp;gt;
    {
        &lt;span style="color: blue"&gt;int &lt;/span&gt;partSum = 0;
        &lt;span style="color: blue"&gt;int &lt;/span&gt;minInclusive = part * n / processorCount;
        &lt;span style="color: blue"&gt;int &lt;/span&gt;maxExclusive = minInclusive + n / processorCount;

        &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;x = minInclusive; x &amp;lt; maxExclusive; x++)
        {
            &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;y = 0; y &amp;lt; n; y++)
            {
                partSum += array[x, y];
            }
        }

        result[part] = partSum;
    });

    &lt;span style="color: blue"&gt;int &lt;/span&gt;sum = 0;
    &lt;span style="color: blue"&gt;for &lt;/span&gt;(&lt;span style="color: blue"&gt;int &lt;/span&gt;i = 0; i &amp;lt; result.Length; i++)
    {
        sum += result[i];
    }

    &lt;span style="color: blue"&gt;return &lt;/span&gt;sum;
&lt;/pre&gt;
&lt;p&gt;与ParallelSumA方法的实现不同，ParallelSumB使用了一个临时变量partSum来保存每次元素相加后的结果，在最后才将partSum，也就是某一块数据计算后的最终结果写入result数组。由于partSum位于不同线程的堆栈上，因此不同线程的partSum变量的地址分布很广，很难被同时读入同一个核的L1 Cache中，自然也不会造成False Sharing。ParallelSumB方法的性能就会较为令人满意，它执行100次所花时间为：&lt;/p&gt;&lt;pre class="code"&gt;00:00:00.5366263&lt;/pre&gt;
&lt;p&gt;上面的例子说明了一点：即使从代码角度来看没有共享任何数据，False Sharing还是可能发生。这迫使我们开发人员在平时工作中需要多留个心眼，偶尔也要考虑一下不同线程频繁修改的数据地址是否非常接近。这需要开发人员对一些“黑盒”内的状况进行适当探索。例如：在.NET程序中，由于托管堆内分配空间的连续性，几乎同时分配的对象地址会比较接近。而且，由于垃圾收集机制在回收时会进行“挤压”，因此生存时间久的对象的地址会愈发接近。可见，即使有了并行库，并行开发依旧不是那么容易。&lt;/p&gt;
&lt;p&gt;回到这片文章的主题。不知道经过这两个示例，朋友们是否更进一步了解到计算机体系结构是如何对开发高性能应用程序，尤其是并行环境下的应用程序产生影响的。类似的事例还有很多，也欢迎您谈谈自己的感想。&lt;/p&gt;
&lt;h1&gt;广告时间&lt;/h1&gt;
&lt;p&gt;老赵还是要强调计算机基础课程的重要性，也因此在这里推广一个课程。这们课叫做ICS，全称叫做Introduction to Computer System，是老赵就读的复旦大学软件学院大二学生的必修课，由软件学院院长臧斌宇教授主讲。使用的教材便是之前提到的CSAPP。更为关键的是，这门课现在已经成为复旦大学的精品课程，其课件、作业、解答、甚至某些课程的录像也都会在网上公开。大家可以在&lt;a title="http://ics.fudan.edu.cn/" href="http://ics.fudan.edu.cn/"&gt;http://ics.fudan.edu.cn/&lt;/a&gt;上访问该课程。&lt;/p&gt;
&lt;p&gt;臧教授是老赵十分佩服的一位德才兼备的教授，还非常年轻。他的实验室在系统、编译、网格计算等方面成绩斐然，严谨的科研氛围也一直让我向往。老赵由他带出来的师兄师姐师弟师妹非常之牛，往往去国内其他科研机构发展的硕士生都会给那里的博士生带来很大压力。他们发布在顶级期刊上的论文不在少数（例如这次在OSDI08里就有他们的科研结果），也经常有人被美国名校录取。&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2009/01/system-architecture-and-program-performance.html#comments</comments>
      <pubDate>Thu, 22 Jan 2009 00:28:00 GMT</pubDate>
      <lastBuildDate>Thu, 22 Jan 2009 00:28:00 GMT</lastBuildDate>
    </item>
    <item>
      <author>jeffz@live.com (老赵)</author>
      <category domain="http://blog.zhaojie.me/parallel/">并行处理</category>
      <category domain="http://blog.zhaojie.me/front-end/">前端表现</category>
      <title>讲座展示：Live From Redmond: Microsoft AJAX Patterns - Implementing Predictive Fetch with Microsoft ASP.NET 2.0 AJAX Extensions</title>
      <link>http://blog.zhaojie.me/2006/12/lecture-lfr-microsoft-ajax-pattern-prediction-fetch.html</link>
      <guid>http://blog.zhaojie.me/2006/12/lecture-lfr-microsoft-ajax-pattern-prediction-fetch.html</guid>
      <description>&lt;p style="font-size: 14pt; font-family: verdana"&gt;&lt;strong&gt;简介&lt;/strong&gt;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;这次我选择的讲座，是最近在Live From Redmond系列WebCast中Joe Stagner的讲座&amp;#8220;Microsoft AJAX Patterns - Implementing Predictive Fetch with Microsoft ASP.NET 2.0 AJAX Extensions&amp;#8221;。Joe Stagner是UI, Tools &amp;amp; Platforms Team的Program Manager。作者在他的Blog上也提供了这个讲座的&lt;a href="http://www.joeon.net/downloads/LFR-PredictiveFetch.zip" target="_blank"&gt;PPT和演示代码下载&lt;/a&gt;（VB.NET编写，不过我想在阅读上应该不会有很大问题）。另外，Microsoft Events也以On-Demand Webcast Events的形式提供了&lt;a href="http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032311345&amp;amp;EventCategory=4&amp;amp;culture=en-US&amp;amp;CountryCode=US&amp;amp;Action=Preview" target="_blank"&gt;这个讲座的完整视频&lt;/a&gt;（不过这个视频从形式到质量都比TechEd和Mix的要差远了）。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;在AJAX形式的Web开发中存在着一些常用的编程模式。在这个讲座里，Joe会讲述并且使用ASP.NET 2.0 AJAX Extensions做一个简单的演示，用来说明&amp;#8220;Predictive Fetch&amp;#8221;模式。&lt;/p&gt;
&lt;fieldset style="font-size: 10pt; font-family: verdana"&gt;&lt;legend&gt;关于Live From Redmond系列&lt;/legend&gt;　　&amp;#8220;Live From Redmond&amp;#8221;是一系列由微软各个产品部门所设计，面向社区的Live Meeting讲座。这些讲座的演讲者都是来自真正的微软产品组，他们都为某个特定的技术工作。这些讲座都是从他们那里获得各种信息的好机会。&lt;/fieldset&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 14pt; font-family: verdana"&gt;&lt;strong&gt;讲座内容&lt;/strong&gt;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;现在已经存在了一系列的有关AJAX的设计模式，或者说是一系列使用AJAX的形式。我们现在来看一下今天要实现的设计模式，这个AJAX模式叫做&amp;#8220;Predictive Fetch&amp;#8221;。我们现在已经有了增加用户体验，改善UI的方式，那就是更新页面的一部分，而避免对于整张页面进行完整的刷新，页面上其余的内容依旧留在页面上。也就是说，我们现在已经有了一个方法，能够使用JavaScript和服务器交换数据，而这个过程可以不为用户所察觉。那么我们现在就有了一个更加好的想法：用户下一步会做什么？我们需要预测用户下一步最有可能执行的操作，例如，最典型的&amp;#8220;Predictive Fetch&lt;br&gt;&amp;#8221;使用场景（不过我们这次不把它作为例子），就是在一篇有许多页的文章里，我们会想：&amp;#8220;现在用户在第一页，他有90%的可能会点击下一页&amp;#8221;、&amp;#8220;如果他已经看到了所有6页中的第3页，那么他会有66%的可能性会查看下一页，而会有33%的可能性会看前一页。&amp;#8221;这样，我们就根据UI的当前状况，我们可以预测用户下一步最有可能会作哪些事情，然后对于所需要的数据进行&amp;#8220;预获取&amp;#8221;。而这时候用户不会察觉到浏览器正在从服务器上获取数据。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;我们在这里可以使用UpdatePanel来做到这一点，不过我更喜欢使用JavaScript从头开始开发——不要误会我的意思，UpdatePanel是个神奇的东西——不过有时候我还是比较乐意使用JavaScript进行开发。UpdatePanel的优点在于它非常容易使用，不过它的缺点在于每一次使用UpdatePanel做页面部分刷新时，都会产生一次完整的PostBack，包括所有的ViewState会被传递到服务器端，而在服务器上也会出现一次完整页面生命周期。我们能够很方便地将一个UpdatePanel拖到页面上产生部分刷新的效果，但是如果我们使用自己的Callback就会避免将页面的大量数据被传递到服务器端，也不会在服务器上出现完整的页面生命周期，因为我们很明确的去访问了一个Service Method，这就会高效许多。这也是我们这次演示会使用的方法。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;使用AJAX的话我们也必须明白，在这里Page Navigation的含义变了。在大多数AJAX形式的应用中，&amp;#8220;前进&amp;#8221;和&amp;#8220;后退&amp;#8221;带来的效果和传统的应用不一样了，比如我父亲——他不会理解到底浏览器的运作方式和使用的技术——有时候他们会被搞糊涂，他们如果想看之前的内容，他们会去点击&amp;#8220;后退&amp;#8221;按钮。现在的AJAX应用都会使用一些方式去&amp;#8220;弥补&amp;#8221;这个问题。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;在AJAX应用中，还必须考虑的一个话题就是&amp;#8220;状态&amp;#8221;。如果我在页面中使用了JavaScript获取了数据并且修改了内容，这些内容往往不会被带到服务器端，我们必须设法保留这些状态。一会儿我会演示该如何做到这些——您能够简单地使用Cookie，但是我在示例中将会使用JavaScript来编写一个简单的状态保存机制。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 14pt; font-family: verdana"&gt;&lt;strong&gt;讲座演示&lt;/strong&gt;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;我是一个拳击迷，所我在这里将会展示的网站，它的作用就是查看运动员的状况。如图（点击小图可查看大图）：&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&lt;a href="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch1%5B1%5D.jpg" atomicselection="true"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="191" src="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch1.jpg" width="240" border="0"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;当我切换下拉框里时，页面会自动地PostBack，以此获得不同的运动员的图片。如图（点击小图可查看大图）：&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&lt;a href="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch2%5B1%5D.jpg" atomicselection="true"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="191" src="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch2.jpg" width="240" border="0"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;如果您想让它看上去更&amp;#8220;AJAX&amp;#8221;一些，您可以使用客户端控件然后编写一些客户端代码，不过这对于我们的示例来说并不重要。这里还有一个按钮来获得这个运动员的资料，我已经&amp;#8220;预测&amp;#8221;您会需要查看这些信息，已经提前获取了。因此在您点击这个按钮时，它们能被很快地显示出来，并且页面没有刷新。如图（点击小图可查看大图）：&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&lt;a href="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch3%5B1%5D.jpg" atomicselection="true"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="186" src="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch3.jpg" width="240" border="0"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;现在按钮已经变成了&amp;#8220;Get Record&amp;#8221;，因此我也&amp;#8220;提前&amp;#8221;获得了这个运动员的战绩。当您点击时，它们也被显示出来了，页面没有刷新和闪烁。如图（点击小图可查看大图）：&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&lt;a href="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch4%5B1%5D.jpg" atomicselection="true"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="186" src="http://img.zhaojie.me/blog/WindowsLiveWriter/aeae4a42ddb4_17EF/PredictiveFetch4.jpg" width="240" border="0"&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;按钮又变成了&amp;#8220;Get Stats&amp;#8221;，这样我们就能在运动员的资料和战绩之间来回切换。可能在本地我们无法看出效果，事实上我在每次得到当前的信息后都会&amp;#8220;预测&amp;#8221;用户会获得下次的信息，因此进行&amp;#8220;预加载&amp;#8221;。当然，如果您选择其它的运动员也一样。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;我们现在看一下应该如何创建这样一个应用。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;自然一开始，依旧是要引入ASP.NET AJAX的功能：引入程序集，编辑web.config文件等等。然后在页面里添加一个ScriptManager，再添加一些必要的元素——一个图片控件、一个下拉框、一个按钮以及最后一个用来显示信息的DIV。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ScriptManager&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ScriptManager1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;div&lt;/span&gt; &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"text-align: center"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;Image&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"FighterPhoto"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt; 
        &lt;span style="color: #ff0000"&gt;ImageUrl&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"~/images/blank_fighter.jpg"&lt;/span&gt;
        &lt;span style="color: #ff0000"&gt;Height&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"223px"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Width&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"162px"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;DropDownList&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ddFighterList"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt;
        &lt;span style="color: #ff0000"&gt;AutoPostBack&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"True"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Selected&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"True"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Value&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"None"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
            Select Athlete ...
        &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;Jeff Roufus&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;Rick Roufus&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;Duane Ludwig&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ListItem&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;DropDownList&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;input&lt;/span&gt; &lt;span style="color: #ff0000"&gt;id&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"btnMore"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"width: 171px"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;type&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"button"&lt;/span&gt;
        &lt;span style="color: #ff0000"&gt;value&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"Get Stats"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;language&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"javascript"&lt;/span&gt; 
        &lt;span style="color: #ff0000"&gt;onclick&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"return btnMore_onclick()"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;br&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;div&lt;/span&gt; &lt;span style="color: #ff0000"&gt;id&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"divDetails"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;style&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"width: 868px; height: 100px"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;div&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;div&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;按理来说，我们应该从数据库里得到这些信息，但是在这里我完全使用了静态的信息——我不想让一个简单的演示也要动用数据库。请注意我们这里的按钮不是一个服务器控件，因为我们会对它们进行客户端的开发，进行&amp;#8220;Predictive Fetch&amp;#8221;，所以我们在这里使用了一个标准的HTML按钮。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;如果您对于ASP.NET AJAX有什么意见的话，您可以写Email给我，我将会把这些功能整理后提交给负责所有功能的GPM。&lt;font color="#ff0000"&gt;&lt;strong&gt;比如在1.0版的ASP.NET中，我们将会提供一个可以将程序集放在Bin目录下的解决方案，这样在没有安装ASP.NET AJAX的虚拟主机下也能使用ASP.NET AJAX的功能了。ASP.NET AJAX的正式版将在12月发布，虽然还没有确定一个时间，但是应该可以在12月份发布它的1.0正式版。&lt;/strong&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;接下去我们将为下拉框在Code-Behind中响应它的事件。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;Protected&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Sub&lt;/span&gt; ddFighterList_SelectedIndexChanged
    (&lt;span style="color: #0000ff"&gt;ByVal&lt;/span&gt; sender &lt;span style="color: #0000ff"&gt;As&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Object&lt;/span&gt;, &lt;span style="color: #0000ff"&gt;ByVal&lt;/span&gt; e &lt;span style="color: #0000ff"&gt;As&lt;/span&gt; System.EventArgs)
    &lt;span style="color: #0000ff"&gt;Handles&lt;/span&gt; ddFighterList.SelectedIndexChanged
    &lt;span style="color: #0000ff"&gt;Select&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; (ddFighterList.SelectedValue)
        &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Jeff Roufus&lt;/span&gt;"
            FighterPhoto.ImageUrl = "&lt;span style="color: #8b0000"&gt;images/Jeff Roufus.jpg&lt;/span&gt;"
        &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Rick Roufus&lt;/span&gt;"
            FighterPhoto.ImageUrl = "&lt;span style="color: #8b0000"&gt;images/Rick Roufus.jpg&lt;/span&gt;"
        &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Duane Ludwig&lt;/span&gt;"
            FighterPhoto.ImageUrl = "&lt;span style="color: #8b0000"&gt;images/Duane Ludwig.jpg&lt;/span&gt;"
        &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Else&lt;/span&gt;
            FighterPhoto.ImageUrl = "&lt;span style="color: #8b0000"&gt;images/blank_fighter.jpg&lt;/span&gt;"
    &lt;span style="color: #0000ff"&gt;End&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Select&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;End&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Sub&lt;/span&gt;&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;然后我们就要开始编写客户端的功能了。再这之前我们还需要编写一个Web Service。它的作用是能够从客户端得到一个运动员的资料或战绩。如下（省去了许多代码）：&lt;/p&gt;
&lt;pre class="code"&gt;&amp;lt;%&amp;#64; WebService Language="&lt;span style="color: #8b0000"&gt;VB&lt;/span&gt;" &lt;span style="color: #0000ff"&gt;Class&lt;/span&gt;="&lt;span style="color: #8b0000"&gt;DataService&lt;/span&gt;" %&amp;gt;
&lt;span style="color: #0000ff"&gt;Imports&lt;/span&gt; System.Web
&lt;span style="color: #0000ff"&gt;Imports&lt;/span&gt; System.Web.Services
&lt;span style="color: #0000ff"&gt;Imports&lt;/span&gt; System.Web.Services.Protocols
&amp;lt;WebService(&lt;span style="color: #0000ff"&gt;Namespace&lt;/span&gt;:="&lt;span style="color: #8b0000"&gt;http://tempuri.org/&lt;/span&gt;")&amp;gt; _
&amp;lt;WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)&amp;gt; _
&amp;lt;Microsoft.Web.Script.Services.ScriptService()&amp;gt; _
&lt;span style="color: #0000ff"&gt;Public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Class&lt;/span&gt; DataService
    &lt;span style="color: #0000ff"&gt;Inherits&lt;/span&gt; System.Web.Services.WebService
    
    &amp;lt;WebMethod()&amp;gt; _
    &lt;span style="color: #0000ff"&gt;Public&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Function&lt;/span&gt; FetchData(&lt;span style="color: #0000ff"&gt;ByVal&lt;/span&gt; strWho &lt;span style="color: #0000ff"&gt;As&lt;/span&gt; &lt;span style="color: #0000ff"&gt;String&lt;/span&gt;, 
        &lt;span style="color: #0000ff"&gt;ByVal&lt;/span&gt; strWhat &lt;span style="color: #0000ff"&gt;As&lt;/span&gt; &lt;span style="color: #0000ff"&gt;String&lt;/span&gt;) &lt;span style="color: #0000ff"&gt;As&lt;/span&gt; &lt;span style="color: #0000ff"&gt;String&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;Dim&lt;/span&gt; strDetails &lt;span style="color: #0000ff"&gt;As&lt;/span&gt; &lt;span style="color: #0000ff"&gt;String&lt;/span&gt;
        strDetails = "&lt;span style="color: #8b0000"&gt;&lt;/span&gt;"
        
        &lt;span style="color: #0000ff"&gt;Select&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; (strWho)
            &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Rick Roufus&lt;/span&gt;"
                &lt;span style="color: #0000ff"&gt;If&lt;/span&gt; strWhat = "&lt;span style="color: #8b0000"&gt;Stats&lt;/span&gt;" &lt;span style="color: #0000ff"&gt;Then&lt;/span&gt;
                    strDetails = ...
                &lt;span style="color: #0000ff"&gt;ElseIf&lt;/span&gt; strWhat = "&lt;span style="color: #8b0000"&gt;Record&lt;/span&gt;" &lt;span style="color: #0000ff"&gt;Then&lt;/span&gt;
                    strDetails = ...
                &lt;span style="color: #0000ff"&gt;End&lt;/span&gt; &lt;span style="color: #0000ff"&gt;If&lt;/span&gt;
            &lt;span style="color: #0000ff"&gt;Case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Jeff Roufus&lt;/span&gt;"
                ...
        &lt;span style="color: #0000ff"&gt;End&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Select&lt;/span&gt;
        
        &lt;span style="color: #0000ff"&gt;Return&lt;/span&gt; strDetails
    &lt;span style="color: #0000ff"&gt;End&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Function&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;End&lt;/span&gt; &lt;span style="color: #0000ff"&gt;Class&lt;/span&gt;&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;我们的FetchData方法接受两个参数，第一个参数表示哪个运动员，第二个参数表示哪项信息，这里还是以拼接字符串的形式将数据拼接成HTML。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;下面我们将编写代码来管理状态。我们必须知道当前页面中已经加载了哪种数据（currentDetails），然后我们才能够进行&amp;#8220;预加载&amp;#8221;。然后我们需要保存那些我们已经获取了，但是还没有被显示在页面上的信息（detailContent）。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;var&lt;/span&gt; currentDetails = "&lt;span style="color: #8b0000"&gt;None&lt;/span&gt;";
&lt;span style="color: #0000ff"&gt;var&lt;/span&gt; detailContent = "&lt;span style="color: #8b0000"&gt;&lt;/span&gt;";&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;然后我们需要写一个JavaScript函数去向Web Service方法&amp;#8220;预加载&amp;#8221;数据。我们的Web Service方法有两个参数，一个是&amp;#8220;Who&amp;#8221;还有一个是&amp;#8220;What&amp;#8221;。&amp;#8220;Who&amp;#8221;的话比较简单，我们只需要获得当前下拉框的值即可，不过获得&amp;#8220;What&amp;#8221;就会相对困难些，我们需要使用一种简单的状态保留的机制，以得知我们需要获取什么样的数据。在这里我们使用了switch分支来判断currentDetails的值。如果是&amp;#8220;None&amp;#8221;，表示我们还没有获得任何数据，那么我们会去&amp;#8220;预加载&amp;#8221;这个运动员的&amp;#8220;资料&amp;#8221;，以此类推。最后我们会调用那个Web Service方法。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; GetDetails() 
{
    &lt;span style="color: #0000ff"&gt;switch&lt;/span&gt;(currentDetails)
    {
        &lt;span style="color: #0000ff"&gt;case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;None&lt;/span&gt;":
            currentDetails = "&lt;span style="color: #8b0000"&gt;Stats&lt;/span&gt;";
            &lt;span style="color: #0000ff"&gt;break&lt;/span&gt;;
        &lt;span style="color: #0000ff"&gt;case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Stats&lt;/span&gt;":
            currentDetails = "&lt;span style="color: #8b0000"&gt;Record&lt;/span&gt;";
            &lt;span style="color: #0000ff"&gt;break&lt;/span&gt;;
        &lt;span style="color: #0000ff"&gt;case&lt;/span&gt; "&lt;span style="color: #8b0000"&gt;Record&lt;/span&gt;":
            currentDetails = "&lt;span style="color: #8b0000"&gt;Stats&lt;/span&gt;";
            &lt;span style="color: #0000ff"&gt;break&lt;/span&gt;;        
    }
    
    fighterDetails = DataService.FetchData(
        &lt;span style="color: #0000ff"&gt;document&lt;/span&gt;.getElementById('ddFighterList').value,
        currentDetails,
        OnComplete,
        OnTimeOut,
        OnError);
} 
&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; OnComplete(retResult) 
{
    detailContent = retResult;
}
&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; OnTimeOut(retResult)
{
    &lt;span style="color: #0000ff"&gt;alert&lt;/span&gt;(retResult);
}
&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; OnError(retResult)
{
    &lt;span style="color: #0000ff"&gt;alert&lt;/span&gt;(retResult);
}&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;自然我们还需要在ScriptManager里引入Web Service的使用。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ScriptManager&lt;/span&gt; &lt;span style="color: #ff0000"&gt;ID&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"ScriptManager1"&lt;/span&gt; &lt;span style="color: #ff0000"&gt;runat&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"server"&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #800000"&gt;Services&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
        &lt;span style="color: #0000ff"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ServiceReference&lt;/span&gt; &lt;span style="color: #ff0000"&gt;Path&lt;/span&gt;=&lt;span style="color: #0000ff"&gt;"DataService.asmx"&lt;/span&gt; &lt;span style="color: #0000ff"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #800000"&gt;Services&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;
&lt;span style="color: #0000ff"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #c71585"&gt;asp&lt;/span&gt;:&lt;span style="color: #800000"&gt;ScriptManager&lt;/span&gt;&lt;span style="color: #0000ff"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;当用户点击按钮时，我们会将&amp;#8220;预加载&amp;#8221;的内容显示在下方的DIV中。然后会再次调用GetDetails方法，因为在显示了信息之后，用户一般会花一段时间查看这些信息，我们应该&amp;#8220;预测&amp;#8221;用户接下来的行为，以进行信息的&amp;#8220;预加载&amp;#8221;。当然最后会改变按钮上的文本，告诉用户接下来能够查看什么数据。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; btnMore_onclick() 
{
    &lt;span style="color: #0000ff"&gt;document&lt;/span&gt;.getElementById('divDetails').innerHTML = detailContent;
    GetDetails();
    &lt;span style="color: #0000ff"&gt;document&lt;/span&gt;.getElementById('btnMore').value = 
        "&lt;span style="color: #8b0000"&gt;Get &lt;/span&gt;" + currentDetails;
}&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;现在还有一个问题，比如我们在选择另一个运动员之后，我们并没有进行任何数据的&amp;#8220;预加载&amp;#8221;，因此我们必须定义一个pageLoad函数。这个函数会被ASP.NET AJAX Client Library在页面被加载时访问。在这个函数中，我们会判断用户是否选择了一个运动员，并进行预加载。如下：&lt;/p&gt;
&lt;pre class="code"&gt;&lt;span style="color: #0000ff"&gt;function&lt;/span&gt; pageLoad () 
{ 
    &lt;span style="color: #0000ff"&gt;if&lt;/span&gt;(&lt;span style="color: #0000ff"&gt;document&lt;/span&gt;.getElementById('ddFighterList').value != "&lt;span style="color: #8b0000"&gt;None&lt;/span&gt;")
    {
        GetDetails();
    } 
}&lt;/pre&gt; 
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&amp;nbsp;&lt;iframe style="DISPLAY: none; VISIBILITY: hidden" src="http://blog.zhaojie.me/2006/11/574530.html" width=0 height=0&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p style="font-size: 14pt; font-family: verdana"&gt;&lt;strong&gt;感想&lt;/strong&gt;&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;事实上我必须承认，这次讲座的质量并不高。这次的讲座，立意不错，可惜内容讲解的比较肤浅，还有这个例子举得实在是比较糟糕，过于简单。Live From Redmond其实也应该算是比较有特色的Web Cast了，可惜其质量——至少这次的质量非常的一般。也有可能是刚经过了TechEd Europe Edition的&amp;#8220;溺爱&amp;#8221;，对于讲座的&amp;#8220;口味&amp;#8221;也变得挑剔了。下一次我将会准备将讲述一下TechEd Europe中的&amp;#8220;DEV370 -&amp;nbsp;AJAX Patterns with ASP.NET AJAX&amp;#8221;，相信大家也能够看出个中差距。&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;另外，我似乎觉得Joe Stagner对于ASP.NET AJAX不怎么熟悉。最近在它的Blog上发了这么一个Post，大意是说他不知道如何设置一个Web Service Call的Timeout，结果从ASP.NET AJAX Team里的一个人那里才知道&amp;#8230;&amp;#8230;我想，园子里已经掌握这点的朋友应该不在少数吧？还有他在上面的例子里，为什么还在使用&amp;#8220;document.getElementById&amp;#8221;方法，而不是&amp;#8220;$get&amp;#8221;&amp;#8230;&amp;#8230;:)&lt;/p&gt;
&lt;p style="font-size: 10pt; font-family: verdana"&gt;&lt;/p&gt;</description>
      <comments>http://blog.zhaojie.me/2006/12/lecture-lfr-microsoft-ajax-pattern-prediction-fetch.html#comments</comments>
      <pubDate>Sun, 03 Dec 2006 15:17:00 GMT</pubDate>
      <lastBuildDate>Sun, 03 Dec 2006 15:17:00 GMT</lastBuildDate>
    </item>
  </channel>
</rss>
