Future of C# and VB - Async, Compiler as a Service

昨晚抽空看了 PDC 2010 Anders Hejlsberg 的演讲,还是一如既往的激情四溢,每次 Demo 完了之后观众都会发出会心的掌声。

Anders 在演讲中介绍了 C# 和 VB 接下来的发展方向(讲解是以 C# 进行的,但他说 VB 将会和 C# 同步发展,具备 C# 包含的新特性)。

演讲的内容包括 Async 和 Compiler as a Service 两部分。

首先 Anders 着重介绍了 Asnyc. 就是对异步编程的支持。

我们知道,在 .NET 中,传统的异步编程大致上是通过两种方式来进行的:

(1) 在发起异步操作的调用中,传入一个回调函数,当异步操作完成后,该函数会被调用到。

(2) 注册一个事件处理函数,异步操作完成时,会通过引发事件的方式执行到该事件处理函数的代码。

这两种方式都有很多弱点 - 他们都是二段式的调用。这就意味着我们用常规的写法,无法轻易的安排多个异步操作之间的次序,依赖关系等等。比如,发起两个异步操作 A 和 B,在他们都完成后接着执行异步操作 C. 概念上很简单的事情,但是因为上面说的二段式调用方式的限制,实际编码起来,我们就需要用设定全局标志变量等方式才能达到目的。而且,异常处理、任务取消、timeout 等控制也变得复杂起来。

在 C# 中,将通过引入两个关键字 asyncawait 来解决这个问题。

例如以下代码:

async Task<Movie[]> QueryMoviesAsync(int year, int first, int count) {
        var client = new WebClient();
        var url = String.Format(query, year, first, count);
        var data = await client.DownloadStringTaskAsync(new Uri(url));
        var movies = 
                // 用一个 linq 查询从 data 中解析出 movies 对象的集合. 视频比较模糊,这个
                // 语句就没有详细记下来,这不是重点。
                from d in data
                ...
                select xxx;
                
        return movies.ToArray();
}

其中 async 用于标注一个方法的返回值,用此关键字标注了的方法,其结果不会立即返回,而是返回了一个表示其返回类型的异步操作的承诺。在将来的某个时刻,其中所包含的异步操作完成时,结果才会被陆续返回。

而 await 关键字,使得线程可以等待一个异步操作完成再继续执行下面的语句。这样,就可以用类似于同步的顺序写法来组织异步代码的次序,完全消除了异步编程的复杂性。

有了这两个关键字,异常处理、任务取消、timeout 的控制逻辑就可以用常规方法来编写了。

异常处理只需要用常规的 try..catch 块就可以完成,不必多说。

任务取消,可以通过向异步方法中传入一个 CancellationTokenSource 对象,然后在适当的时候调用其 Cancel 方法即可。

而 Timeout 和取消非常类似。代码示例:

async void StartTimeoutAsync() {
        await TaskEx.Delay(5000);
        if (cts != null) {
                cts.Cancel();
                statusText.Text = "Cancelled";  
        }
}

private void searchButton_Click(object sender, RoutedEventArgs e){
        // 启动下载任务
        LoadMoviesAsync(Int32.Parse(textBox.Text));
        // 启动定时器,执行5秒后取消下载
        StartTimeoutAsync();
}

另一个例子:

async void ShowDateTimeAsync(){
        while (true) {
                Title = "Movie Finder " + DateTime.Now;
                await TaskEx.Delay(1000);
        }
}

这段代码会在窗体的标题栏上显示当前时间,每秒钟刷新一次。

上面的代码都没有使用多线程。那么何时该使用多线程呢?当你需要执行计算密集的任务时,多线程才是必要的。简而言之,多线程应该用来充分利用 CPU 资源,而不是用来空置着等待 I/O 任务的完成。

(Anders: So when do I need more threads? you need threads when you gonna do some computational intensive work, when you need the power of CPU.)

对于 Async 扩展而言,不管是 I/O 密集型还是计算密集型的代码,都可以通过类似的方式来调用。下面是一个例子,其中可能会分配一个线程池线程来执行计算,而 UI 线程则通过 await 关键字等待直到计算完成:

async void ComputeStuffAsync(){
    double result = 0;
    await TaskEx.Run(()=>{
        for (int i = 1; i < 500000000; i++) {
            result += Math.Sqrt(i);
        }
    });
    MessageBox.Show("the result is " + result,
        "Background Task",
        MessageBoxButton.OK, MessageBoxImage.Information);
}

Anders 还简要介绍了 Async 扩展的实现原理。其大致思想,是通过编译器,将 asnyc / await 关键字等组织的代码,编译为状态机 (state machine) 来控制执行流程。当多个异步操作互相调用,形成一系列异步操作组成的组合操作时,编译器就会将其编译为通过 continuation 的方式串接起来的一个一个的操作序列。这里创建状态机的思想其实和 C# 2.0 yield 关键字的实现如出一辙,但是编译器在背后所做的工作更加复杂化和智能化了。

感觉上,C# Async 的功能实现了类似于 F# 中的异步工作流的优秀特性。在 Anders Hejlsberg 的指引下,我们有理由对 C# 的未来充满信心,其一定会不断实现和采纳其他语言所具有的各种优秀的语言特性到 C# 中,从而使 C# 编码更加爽快和便捷。

在讲座结束前,Anders 还花一点时间介绍了 Compiler as a Service. 就是将编译器开放为一种可在代码中调用的服务。其中一个 demo 却因为 Visual Studio 的什么原因而导致演示失败了。

对并发编程的支持:在提问环节,Anders 回答说 Concurrency 目前还是以库的方式提供支持 (.NET 4.0),而没有实现为语言特性。在语言支持的方面目前也在进行着一些努力。

欲了解详情,可下载 Visual Studio Async CTP 进行体验。

PS: 老赵已对 Anders 的这次演讲进行了更为详尽的解读,强烈推荐大家阅读老赵的文章,以获得全面的了解。