.NET Framework

探索 Microsoft .NET Framework 4.5.1

Gaye Oncul Oncul

Microsoft .NET Framework 4.5.1 版本以及 Visual Studio 2013 引入了一些具有创新意义的功能,旨在提高开发人员的工作效率和应用程序的性能。此外,它还提供了一些新功能来改进使用 .NET NuGet 程序包的用户体验,这是非常重要的,因为 NuGet 是 .NET Framework 库的主要交付途径。

之前的产品 .NET Framework 4.5 是一个很重要的版本,它引入了很多新功能。现在它已安装在 2 亿多台机器上。2013 年 10 月,.NET Framework 4.5.1 发布仅约 14 个月后,它就应客户请求封装了很多的功能。在本文中,我会介绍 .NET Framework 4.5.1 中的新功能。要了解更多详细信息,可参阅 .NET Framework 博客中有关 .NET Framework 4.5.1 RTM (bit.ly/1bBlEPN) 和 .NET Framework 4.5.1 Preview (bit.ly/10Vr2ft) 的帖子。

.NET Framework 4.5.1 只不过是 .NET 团队(我隶属于此团队)在过去一年中的一部分工作。我们还通过 NuGet 提供了几个库,以填补平台空白和支持新的情形。我将简要介绍我们的 .NET NuGet 库,同时重点介绍我们的一项重要投资,即全新的 .NET 即时 (JIT) 编译器,它作为社区技术预览版 (CTP) 几乎与 .NET Framework 4.5.1 同时发布。

提高开发工作效率

我将开始使用 .NET Framework 4.5.1 所提供的新调试功能来提高开发人员的工作效率。

异步调试改进 在之前的 Framework 版本中,我们已经打下了牢固而易于使用的异步编程模型基础,我们希望能在 .NET Framework 4.5.1 中改进其他的一些方面,改善全体开发人员的体验。对于调试异步代码而言,有两个基本问题:“我如何深入调试这种异步方法?”以及“我应用程序中所有任务的状态是怎样的?”Visual Studio 2013 中增强了“调用堆栈”和“任务”窗口,以帮助您更直观地找到这些问题的答案。Windows 8.1 中的桌面、Web 和 Windows 应用商店的应用都支持这些改进,这些改进还可用于 C++ 和 JavaScript。

在应用程序或库中使用嵌套异步方法调用是很常见的,这些调用要依赖 await 关键字来管理执行流。以前,当执行流在一个任务中的断点处停止时,Visual Studio 并不会显示异步调用链。Visual Studio 2013 通过异步和同步方法的嵌套调用链提供了方法的逻辑和顺序视图。这使得用户能够更轻松地了解程序是如何到达异步调用中的某个位置的。

图 1 显示了一个异步代码的示例。图 2图 3 显示了 Visual Studio 2012 和 Visual Studio 2013 中此段代码的调用堆栈视图之间的区别。在 bit.ly/19NTNez 的“在 Visual Studio 2013 中调试一步代码 - 调用堆栈增强功能”博客帖子中,您可以找到有关此功能的更多信息。

图 1 异步代码示例

private async void ShowSampleImg_Click(object sender, 
    RoutedEventArgs e)
{
  string imgUri = "http://example.com/sample.jpg";
  BitmapImage bitmap = new BitmapImage();
  bitmap.BeginInit();
  bitmap.StreamSource = await GetSampleImgMemStream(imgUri);
  bitmap.EndInit();
  sampleImg.Source = bitmap;
}
private async Task<MemoryStream> GetSampleImgMemStream(string srcUri)
{
  Stream stream = await GetSampleImage(srcUri);
  var memStream = new MemoryStream();
  await stream.CopyToAsync(memStream);
  memStream.Position = 0;
  return memStream;
}
private async Task<Stream> GetSampleImage(string srcUri)
{
  HttpClient client = new HttpClient();
  Stream stream = await client.GetStreamAsync(srcUri);
  return stream;
}

Visual Studio 2012 Call Stack Window
图 2 Visual Studio 2012 调用堆栈窗口

Visual Studio 2013 Call Stack Window
图 3 Visual Studio 2013 调用堆栈窗口

Visual Studio 2013 中的“任务”窗口能够显示当前正在运行的所有任务和已调度的任务,旨在帮助您了解您的应用程序中异步任务的状态。它替代了在以前的 Visual Studio 版本中提供的“并行任务”窗口。图 4 显示了图 1 中所提供的示例代码在 Visual Studio 2013 中的“任务”窗口的快照。

Visual Studio 2013 Tasks Window
图 4 Visual Studio 2013“任务”窗口

x64 编辑并继续这是一个用户广泛提出的调试器功能要求,在用户可以提出新功能要求的 Visual Studio UserVoice 网站 (bit.ly/14YIM8X) 上获得了 2,600 多的投票票数。自从 Visual Studio 2005 以及 .NET Framework 2.0 版本中引入面向 x86 项目的“编辑并继续”功能以来,这一功能深受开发人员喜爱。使用“编辑并继续”功能,您可以在调试会话中更改源代码,同时应用程序状态仍为可用,因此您能够更轻松地编写出正确的代码。您甚至还能移动指令指针,以便在做出更改后重新运行代码。它提供了更高效的开发体验,因为您不必停止再重新启动这一会话来验证您的更改。

Visual Studio 2013 和 .NET Framework 4.5.1 版本现在支持对 x64 使用“编辑并继续”功能。您可以使用此功能来调试面向 x64、AnyCPU 或 x86 体系结构的桌面应用程序(Windows Presentation Foundation、Windows 窗体等等)、Windows 应用商店的应用、ASP.NET Web 应用程序以及 Windows Azure 云服务项目。

托管返回值检查 调试器针对托管返回值的支持是应 UserVoice 网站上超过 1,000 份投票的另一项用户广泛提出的请求所开发的功能。Visual C++ 调试器有一个现有的功能,您可用它来查看方法的返回值,我们希望 .NET 也能使用相同功能。对于很多代码模式来说,此功能都很有用。但是,您可以通过像图 5 中演示的嵌套方法来查看其值。使用此功能,您不必再考虑只将方法结果存储在局部变量中以简化调试过程。当您单步执行方法调用时,直接返回值和嵌入式方法的返回值都将与传递给函数的参数值一起显示在“自动”窗口中。您还可以使用新的 $ReturnValue 伪变量在“即时”窗口中访问上一个返回值。

Visual Studio 2013 Autos and Intermediate Windows
图 5 Visual Studio 2013“自动”和“即时”窗口

Windows 应用商店开发增强功能 我们响应了客户的反馈意见,为新的 Windows 运行时 (WinRT) 功能提供了 .NET 支持来改进 .NET Windows 应用商店的应用开发体验。

有一个难题是将 .NET 流转换为 WinRT IRandomAccessStream。在 .NET Framework 4.5.1 中,我们为 System.IO.Stream 增加了一个新的扩展方法 AsRandomAccessStream 来解决这个问题。您现在可以编写以下代码,这样就能轻松获得 IRandomAccessStream 了:

// EXAMPLE: Get image from URL via networking I/O
var client = new HttpClient();
Stream stream = await client.GetStreamAsync(imageUrl);
var memStream = new MemoryStream();
await stream.CopyToAsync(memStream);
memStream.Position = 0;
var bitmap = new BitmapImage();
bitmap.SetSource(memStream.AsRandomAccessStream());
image.Source = bitmap;

这段示例代码从 Web 中读取一个图像,将它显示在 XAML Image 控件(用“image”变量表示)中。

Windows 运行时中的另一个改进是错误传播。Windows 8.1 中的 Windows 运行时支持在 WinRT 组件之间传递异常。借助这一支持,可以从 C++ WinRT 组件中引发异常,然后在 C# 中捕获此异常(或者以相反方向操作)。现在可通过 System.Exception 的 Message 和 StackTrace 属性提供有关异常的其他信息。

Windows 运行时还增加了对结构中可以为 null 的值类型的支持。您可以使用此新功能构建公开结构的托管 WinRT 组件,就像以下示例代码一样:

public struct PatientRecord
{
  public string Name;
  public int Age;
  public string HomeAddress;
  // InsuranceID is nullable
  public int? InsuranceId;
}

更高的应用程序性能

提高应用程序性能始终是 .NET Framework 团队的工作重点。在此版本中,我们应客户有关垃圾回收器的反馈意见,大大改进了 ASP.NET 应用程序的启动。

ASP.NET 应用程序暂挂 此功能是 .NET Framework 4.5.1 的一个极为重要的功能,因为它大大提高了性能,尤其是在站点密度和启动延迟都非常关键的情形下的性能。ASP.NET 应用程序暂挂将使共享托管方(商业 Web 托管公司或企业 IT 系统)能够在更短的应用程序启动时间内托管更多的 ASP.NET 网站。

ASP.NET 应用程序暂挂功能的实施要依赖于 IIS 空闲工作进程页面调出,这是 Windows Server 2012 R2 中的一个新 IIS 功能。IIS 空闲工作进程页面调出在现有的网站“不活动”和“活动”状态之外又引入了一个新的“已暂挂”状态。这一全新的“已暂挂”状态释放站点所使用的关键资源(特别是 CPU 和内存资源)供其他站点使用,同时仍然支持快速恢复该站点。

图 6 显示了使用“应用程序暂挂”功能对 ASP.NET 站点进行的状态转换。网站一开始为不活动状态。它加载到内存中,在接到首次页面请求时转换为活动状态。一段时间的空闲之后,将按照应用程序池配置 (bit.ly/1aajEeL) 暂挂该网站。当接到对该网站的后续请求时,它会快速返回到活动状态。这个过程可以发生很多次。现在,网站可终止,在一段时间的空闲之后会变为不活动状态。

The State Transitions of ASP.NET Web Sites
图 6 ASP.NET 网站的状态转换

使用此新功能无需更改代码。在 Windows Server 2012 R2 中为“暂挂”配置 IIS 应用程序池后,将自动启用“ASP.NET 应用程序暂挂”功能。

之前我极力称赞了通过此功能实现的“重大性能提升”,我想借助我们的性能实验室所提供的一些数据来证明这一点。我们开展了大量的性能实验来测量并比较“暂挂后恢复”与“终止后启动”的启动时间。我们在具有繁重请求负载的机器上进行了这些实验,并在实验时访问大量的应用程序池,这一切都是为了重新创造一个“共享托管”环境。结果显示,暂挂后访问的网站的启动时间缩短 90%。我们还度量了站点密度方面的改进。在启用 ASP.NET 应用程序暂挂功能的情况下,我们发现可以在 Windows Server 2012 R2 上托管相当于原来 7 倍之多的 ASP.NET 网站。图 7 显示了这些实验的结果。在 bit.ly/17fI6dM 的“ASP.NET 应用程序暂挂 - 响应迅速的共享 .NET Web 托管”博客帖子中,您可以找到对于这些实验更多的见解。

ASP.NET App Suspension Performance Numbers Seen in the .NET Lab
图 7 在 .NET 实验室中观察到的 ASP.NET 应用程序暂挂性能数据

多核心 JIT 编译增强功能 现在对 ASP.NET 应用程序默认可启用多核心 JIT 编译。性能测量结果显示,在启用多核心 JIT 的情况下,冷启动时间可缩短多达 40%。通过在多个核心上并行执行代码和 JIT 编译,可提供启动优势。实际上,多核心 JIT 是为了支持动态加载的程序集(在 ASP.NET 应用程序中很常见)而扩展的。这一项支持也使客户端应用程序受益,多核心 JIT 仍然是一项可选功能。在 bit.ly/RDZ4eE 的一篇相关的 .NET Framework 博客帖子“提高应用程序启动性能的一个简便解决方式”中提供了有关多核心 JIT 功能的更多详细信息。

按需大对象堆 (LOH) 压缩 LOH 压缩在一些情况下非常有必要,在此版本中提供了这一功能。首先我要介绍一点儿背景信息,因为您可能并不熟悉 LOH。垃圾回收器将大于 85,000 字节的对象存储在 LOH 中。LOH 可能会变成很多碎片,在某些情况下这可能导致相对较大的堆大小,甚至导致 OutOfMemoryException 异常。这种情况虽然罕见,但仍然可能发生,因为即使可能从总量上来说存在足够空间,但 LOH 中还是没有足够的可用连续内存块能满足分配请求。

使用 LOH 压缩技术,您可以回收并合并未使用的较小内存块,让这些内存块可用于较大的空间分配,从而能够从总体上更好地利用机器的内存。虽然这种想法听起来非常诱人,但此功能并不用于普通用途。压缩 LOH 是一个代价高昂的过程,可能在应用程序中导致长时间的停顿,因此只应该在分析和测试之后才部署到生产环境。

.NET Framework NuGet 库更易于使用

我们会更频繁地提供 .NET Framework 版本,更快地提供新功能和修补程序。实际上,在 .NET Framework 4.5.1 中我们就已经开始了这些工作。此外,我们把 NuGet 用作一种发布途径来更快交付我们的库功能和修补程序,以响应客户的反馈意见。

NuGet 是 .NET Framework 的一种较新的程序包形式。它提供了一种标准格式来将面向一个或多个 .NET 配置文件的库打包,并可通过 Visual Studio 等开发人员工具一致使用。NuGet.org 是主要的 NuGet 存储库,也是 .NET 团队使用的唯一一个存储库。Visual Studio 附带了一个集成的 NuGet 客户端,可用于在您的项目中引用和使用 NuGet 程序包。

在过去的几年中,我们一直在通过 NuGet 提供 .NET 库。我们发现,NuGet 是同时向大量开发人员和多种 .NET 平台交付库的一种很好的方式。我们基于广大用户的反馈意见在 Visual Studio 2013 中改进了 NuGet 用户体验,针对企业使用场景做出了尤其大的改进。

更强的可发现性和正式支持 Microsoft 和 .NET NuGet 订阅源旨在让用户更容易发现 Microsoft 程序包。NuGet.org 托管了数千个程序包,因此要在众多程序包中发现新的 .NET 程序包可能比较困难。NuGet.org 上这个能提供帮助的全新订阅源为您提供了正式的 Microsoft 和 .NET 程序包的经过范围界定的视图。我们只会把符合与 .NET Framework 一样的质量与支持要求的程序包添加到此订阅源。因此,您在使用 .NET API 的所有位置也可以使用这些程序包。我们还在 .NET Framework 博客的“Microsoft .NET Framework NuGet 程序包”页面 (bit.ly/19D5QLE) 中创建了此订阅源的 Web 视图。

NuGet 团队在 Visual Studio 中更新了客户端,在其中包括了按经过组织的订阅源进行筛选的功能,从而帮助我们获得了这个体验。图 8 显示了 Visual Studio 2013 中的 NuGet 客户端。

The NuGet Client in Visual Studio 2013
图 8 Visual Studio 2013 中的 NuGet 客户端

可服务性 一些企业客户告诉我们,他们一直在等待我们通过 Microsoft Update 为这些库提供中心服务,以便采用我们的 NuGet 程序包。在 .NET Framework 4.5.1 中我们增加了这个更新功能,因而应用程序可以利用此新功能。在一些情况(尽管不太可能发生)下,我们需要快速并广泛地针对某个关键的安全问题更新库,Microsoft Update 将是 .NET NuGet 库的又一个发布途径。即使现在可以使用这种新的选择,我们也仍然会继续使用 NuGet 作为库更新和修补程序的主要发布方式。

自动解决版本冲突问题 应用程序可以引用多个 NuGet 程序包版本。对于桌面和 Web 应用程序,您以前需要手动解决版本冲突以确保运行时加载一致的一组库,这可能很困难而且带来很多不便。为解决这个问题,Visual Studio 2013 可将应用程序自动配置为使用每个库被引用得最多的版本,这样就能够通过这一种直接的策略来解决问题。它还与针对 Windows Phone 和 Windows 应用商店的应用程序已在使用的策略匹配。

如果在应用程序中发现冲突,则 Visual Studio 2013 将在生成时自动在 app.config 生成绑定重定向。这些绑定重定向会将对于给定库所发现的每一个版本都映射到发现的最高版本。在运行时,您的应用程序对每个库只会使用一个版本,即被引用的最高版本。提供此功能的主要目的是提供更好的 NuGet 库使用体验;但是,此功能对任何库都有效果。MSDN 库中“如何:启用和禁用自动绑定重定向”主题 (bit.ly/1eOi3zW) 中提供了有关此功能的更多详细信息。

更多功能...

现在,我已经总结了 .NET Framework 4.5.1 版本中提供的功能。在这一段时间内,我们还通过其他发布途径提供了一些重要的新组件和功能。

HTTP 客户端库 NuGet 程序包 HTTP 客户端库提供一致、新式的网络连接 .NET API。您可以用它编写直观的异步代码(使用 await 关键字),以使用直接与 HTTP 原语(如 GET、PUT、POST 和 DELETE)直接对应的方法名访问通过 HTTP 公开的服务。它还以 String、Stream 或 Byte[] 字节形式提供对 HTTP 头以及响应正文的直接访问。

首先,HttpClient 只能用于 .NET Framework 4.5 桌面和 Windows 应用商店的应用。可移植的库和 Windows Phone 应用程序开发人员以前必须将 HttpWeb­Request 和 HttpWebResponse 与他们的非基于任务的异步模式 (TAP) 模型配合使用。基于对可移植库和 Windows Phone 支持的广大需求,我们在 NuGet 上提供了 HttpClient 库的可移植版本,来填补这一平台空白。这样,所有 .NET 开发人员都能够通过其 TAP 异步 API 访问 HttpClient。

发布最初几个版本的 HttpClient NuGet 程序包后,我们应客户反馈意见,增加了自动解压缩功能 (bit.ly/13xWATe)。HTTP 响应的自动解压缩功能有助于最大程度地减少数据需要,这不仅在移动设备上很有用,还能帮助改进在桌面上的性能体验。

NuGet 上的 Microsoft HTTP 客户端库 (bit.ly/1a2DPNY) 已经获得很多客户采用,已有超过 130 万次下载。您可以在面向 Windows Phone 7.5 和更高版本、Silverlight 4 和更高版本、.NET Framework 4 和更高版本、Windows 应用商店以及可移植类库 (PCL) 的应用程序中使用此程序包。

Microsoft 不可变集合 NuGet 程序包 这是另一个广受欢迎的 .NET 程序包,它提供了易于使用、高性能的不可变集合,如 ImmutableList<T> 和 ImmutableDictionary<TKey, TValue>。不可变集合一旦构建完毕就不能再修改。这支持跨线程或异步上下文传递不可变类型,而无需担心并发操作。即使是集合的初始创建者也无法添加或删除项。

.NET Framework 有只读集合类型,如 ReadOnlyCollection<T> 和 IReadOnlyList<T>。这些类型可确保客户无法更改数据。但是,对于提供者就没有类似的保证了。如果提供者和使用者在不同线程中同时操作,那么这可能会导致数据损坏。使用不可变集合类型,您一定能获得一个永不更改的给定实例。

Microsoft 不可变集合 NuGet 程序包 (bit.ly/18xhE5W) 以可移植库的形式提供,可在面向 .NET Framework 4.5 和更高版本及 PCL 的桌面和 Windows 应用商店的应用程序以及 Windows Phone 8 应用程序中使用。要想了解更多见解和详细信息,我建议您从 .NET Framework 博客中的“不可变集合已为实现最佳状态做好准备”(bit.ly/18Y3xp8) 以及 bit.ly/189XR9U 上的 MSDN 文档着手。

全新 .NET JIT 编译器 RyuJIT JIT 编译器是我们的一个重要投资领域,旨在提高应用程序性能。.NET 团队最近宣布发布下一代 x64 JIT 编译器的 CTP 版本,代号“RyuJIT”。RyuJIT 比现有的 x64 JIT 编译器的代码编译速度快一倍,这意味着使用 RyuJIT 的应用程序启动速度最多可以加快 30%,具体加快程度要取决于在 JIT 编译上所花费的启动时间的百分比。(请注意,在 JIT 编译器中花费的时间只是启动时间的一部分,因此应用程序并不会因为 JIT 的速度提高到两倍,就能够将启动速度提高到两倍。)同时,RyuJIT 并不会危害到代码质量,相反,这一新式的 JIT 编译器为未来的代码质量优化开拓了更多的道路。

除提高了性能之外,RyuJIT 还突出体现了 .NET 团队对于客户参与的重视。CTP 发布之后不到一个月,我们就又发布了一个更新版本,其中融入了客户的反馈意见。我们将继续深化客户参与,快步实现改进。

我们在打造一流的云平台的过程中,将 x64 作为重点启动了 RyuJIT 的开发。随着团队工作的逐步进展,我们将为其他体系结构提供支持。您可以在bit.ly/19RvBHf 上的“RyuJIT:下一代 .NET JIT 编译器”帖子中了解有关 RyuJIT 项目的更多详细信息以及如何下载和使用 CTP。我建议大家实际试一试并向我们提供反馈。

期待您的反馈

在本文中,我简要介绍了 .NET Framework 4.5.1 版本中的新功能。.NET 团队提供了用户请求的很多重要功能,此外还为大家带来一些具有创新意义的惊喜,如“ASP.NET 应用程序暂挂”和可识别异步模式的调试。

我们将通过通常跨越多个 .NET 版本的项目在一些重要方面(如 JIT、垃圾回收和库)塑造未来 .NET 的功能。在本文中,我还提供了我对其中一项重大投资,即全新 .NET JIT 编译器 RyuJIT 的见解。此编译器最近作为 CTP 版本发布。

请注意,.NET 团队将积极地倾听您的反馈意见。您可以关注 .NET 新闻,并通过以下渠道为该团队提供您的宝贵反馈意见:

Gaye Oncul Kok 是 Microsoft 的 CLR 和 .NET Framework 项目经理,她参与 .NET Ecosystem 团队的工作。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Habib Heydarian、Richard Lander、Immo Landwerth、Andrew Pardoe、Subramanian Ramaswamy 和 Alok Shriram
Richard Lander 从 .NET 2 发布以来一直担任 .NET 团队的项目经理。他最喜爱的 .NET 功能是泛型和 lambda。

Immo Landwerth 是 Microsoft CLR 团队的项目经理,负责 Microsoft .NET Framework 基类库 (BCL)、API 设计和可移植类库方面的工作。

Andrew Pardoe 是 .NET Runtime 团队的项目经理。他的团队负责 .NET Framework 的虚拟执行环境的各个方面。

Subramanian Ramaswamy 是 .NET CLR 团队的一位资深项目经理。他于 2008 年加入 Microsoft,目前的工作是运行时中的代码执行策略。他获得了乔治亚理工学院电子与计算机工程的博士学位。他还撰写了多篇会议论文和 MSDN 杂志文章。

Alok Shriram 是 Microsoft .NET Framework 团队的项目经理,在此之前他是 Office 365 团队的开发人员。他的工作包括托管可扩展性框架 (MEF) 、DotNet 框架、NuGet 程序包,还有为开发人员带来其他好处的开发工作。