本文章是由機器翻譯。

非同步程式設計

將非同步 TCP 通訊端當作 WCF 的替代方法

James McCaffrey

下载代码示例

在微軟的技術環境中,使用 Windows 通信基礎 (WCF) 是創建一個用戶端-伺服器系統的常用方法。有許多其他辦法 WCF,當然,每個都有自己的優點和缺點,包括 HTTP Web 服務、 Web API、 DCOM,AJAX Web 技術、 具名管道程式設計和原始 TCP 通訊端程式設計。但如果你考慮到發展努力、 可管理性、 可擴充性、 性能和安全等因素,在許多情況下,使用 WCF 是最有效的辦法。

但是,WCF 可以非常複雜,可能有些矯枉過正程式設計的情況。之前版本的 Microsoft.NET 框架 4.5,非同步通訊端程式設計是,在我看來,在大多數情況下,其使用的理由太難。但便於使用的新的 C# 等待和非同步語言功能改變平衡,因此使用的通訊端程式設計的非同步用戶端-伺服器系統現在是比以前要更有吸引力的選項。這篇文章解釋了如何使用這些新的非同步功能的創建低級,.NET 框架 4.5 高-­性能的非同步用戶端-伺服器的軟體系統。

看看哪裡的最佳方法是,看看所示的演示用戶端-伺服器系統圖 1。在圖像的頂部命令外殼程式正在運行非同步 TCP 通訊端基於服務的接受請求來計算平均或最少的一組的數位值。在圖像的中間部分是一個 Windows 表單 (WinForm) 應用程式發送了一個請求來計算 (3、 1、 8) 的平均值。請注意,用戶端是非同步 — — 發送的請求在等待服務回應,使用者就能夠按一下按鈕標記為 Say Hello 三次,該應用程式是回應後。


圖 1 演示基於 TCP 的服務與兩個用戶端

下半部分的圖 1 顯示 Web 應用程式用戶端中的行動。用戶端已發送一個非同步請求來查找最小值 (5、 2、 7、 4)。雖然它不是從螢幕截圖,可以明顯看出雖然 Web 應用程式正在等待服務回應,應用程式是回應使用者輸入。

在後面的部分,我將展示如何在 WinForm 用戶端、 服務和 Web 應用程式用戶端的代碼。一路上,我將討論使用通訊端的利弊。本文假定您有至少中間級 C# 程式設計技能,但不是承擔你有深刻的理解或非同步程式設計的重要經驗。伴隨著這篇文章的代碼下載已在所示的三個程式的完整原始程式碼圖 1。我已刪除了最正常的錯誤檢查,以保持盡可能清晰的主要思想。

建立服務

演示服務,與一些少量的編輯,以節省空間的總體結構介紹在圖 2。若要創建服務,我發起了Visual Studio2012 年,具有所需的.NET 框架 4.5,並創建一個新 C# 主控台應用程式命名為 DemoService。因為基於通訊端的服務往往具有特定的、 有限的功能,使用更具描述性的名稱將在現實生活的情況下更可取。

圖 2 演示服務程式結構

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading.Tasks;
namespace DemoService
{
  class ServiceProgram
  {
    static void Main(string[] args)
    {
      try
      {
        int port = 50000;
        AsyncService service = new AsyncService(port);
        service.Run();
        Console.ReadLine();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
      }
    }
  }
  public class AsyncService
  {
    private IPAddress ipAddress;
    private int port;
    public AsyncService(int port) { .
.
}
    public async void Run() { .
.
}
    private async Task Process(TcpClient tcpClient) { .
.
}
    private static string Response(string request)
    private static double Average(double[] vals) { .
.
}
    private static double Minimum(double[] vals) { .
.
}
  }
}

載入到編輯器中的範本代碼後,我修改了使用頂部的原始程式碼,包括 System.Net 和 System.Net.Sockets 語句。 在解決方案資源管理器視窗中,我重 Program.cs 檔命名為 ServiceProgram.cs 和Visual Studio自動重命名類的程式,對我來說。 啟動服務很簡單:

int port = 50000;
AsyncService service = new AsyncService(port);
service.Run();

每個自訂的基於通訊端的服務在伺服器上必須使用唯一的埠。 49152 到 65535 之間的埠號通常用於自訂服務。 避免埠編號碰撞可能會非常棘手。 它是可能保留使用系統登錄條目 ReservedPorts 的伺服器上的埠號。 該服務使用一種物件導向的程式設計 (OOP) 設計,通過接受埠號的建構函式具現化。 因為服務的埠號固定的埠號可以是硬編碼而不是作為一個參數傳遞。 Run 方法包含一段時間迴圈將接受和處理用戶端請求,直到主控台 shell 接收 < 輸入 > 按下鍵。

AsyncService 類有兩個私有成員、 ip 位址和埠。 基本上,這兩個值定義一個通訊端。 建構函式接受一個埠號,並以程式設計方式確定伺服器的 IP 位址。 公共方法運行沒有接受請求,然後計算和發送回應的所有工作。 Run 方法調用説明器方法過程,反過來調用説明器回應。 方法回應要求傭工平均和最小值。

有許多方式來組織基於通訊端的伺服器。 在演示中使用的結構嘗試模組化和簡單起見,之間取得平衡,並曾對我在實踐中。

服務構造和運行的方法

在介紹了基於通訊端的演示服務的兩個公共方法圖 3。 存儲埠的名稱後, 建構函式使用方法 GetHostName 以確定伺服器的名稱,然後獲取一個結構,包含有關伺服器的資訊。 AddressList 集合包含不同的機器位址,包括 IPv4 和 IPv6 位址。 網際網路枚舉值表示的 IPv4 位址。

圖 3 服務構造和運行的方法

public AsyncService(int port)
{
  this.port = port;
  string hostName = Dns.GetHostName();
  IPHostEntry ipHostInfo = Dns.GetHostEntry(hostName);
  this.ipAddress = null;
  for (int i = 0; i < ipHostInfo.AddressList.Length; ++i) {
    if (ipHostInfo.AddressList[i].AddressFamily ==
      AddressFamily.InterNetwork)
    {
      this.ipAddress = ipHostInfo.AddressList[i];
      break;
    }
  }
  if (this.ipAddress == null)
    throw new Exception("No IPv4 address for server");
}
public async void Run()
{
  TcpListener listener = new TcpListener(this.ipAddress, this.port);
  listener.Start();
  Console.Write("Array Min and Avg service is now running"
  Console.WriteLine(" on port " + this.port);
  Console.WriteLine("Hit <enter> to stop service\n");
  while (true) {
    try {
      TcpClient tcpClient = await listener.AcceptTcpClientAsync();
      Task t = Process(tcpClient);
      await t;
    }
    catch (Exception ex) {
      Console.WriteLine(ex.Message);
    }
  }
}

這種方法限制要偵聽請求使用只有伺服器的第一次分配的 IPv4 位址的伺服器。 更簡單的方法可以使伺服器能夠接受要求傳送給它的位址的任何的只分配作為 this.ipAddress 的成員欄位 = IPAddress.Any。

請注意該服務的運行方法簽名使用 async 修飾符,它指示在方法體中,將結合等待關鍵字調用一些非同步方法。 因為運行調用的 Main 方法中,其中,作為一種特殊情況,不允許 async 修飾符,該方法返回 void 而不是更為常見的任務。 替代方法是定義方法返回類型的任務,然後調用該方法作為服務運行。Run ()。等。

該服務的運行方法具現化一個 TcpListener 物件,使用該伺服器的 IP 位址和埠號。 攔截器的 Start 方法開始監測指定的埠,等待連接請求。

While 迴圈主要加工,裡面一個 TcpClient 物件,你可以認為是一個智慧的插座,創建並等待通過 AcceptTcpClientAsync 方法連接。 在.NET 框架 4.5 之前, 你必須使用 BeginAcceptTcpClient,然後編寫自訂非同步協調代碼,其中,相信我,不是簡單。 .NET 框架 4.5 添加許多新的方法,由公約 》,以結尾的"非同步"。這些新的方法,結合非同步和等待的關鍵字,使非同步程式設計很容易地。

方法運行調用方法使用兩個語句的進程。 替代方法是使用快捷方式語法和調用方法過程在單個語句中:等待 Process(tcpClient)。

若要匯總,服務使用 TcpClient 和 TcpListener 物件來隱藏原始通訊端程式設計的複雜性和新異步結合使用新的 AcceptTcpClientAsync 方法,等待關鍵字,以隱藏非同步程式設計的複雜性。 方法回合設定了和協調活動的連接,並調用方法過程來處理請求,然後第二個語句,等待返回任務上。

服務流程及回應方法

服務物件的過程和反應方法按圖 4。 過程方法的簽名使用 async 修飾符和返回類型的任務。

圖 4 演示服務進程和回應方法

private async Task Process(TcpClient tcpClient)
{
  string clientEndPoint =
    tcpClient.Client.RemoteEndPoint.ToString();
  Console.WriteLine("Received connection request from "
    + clientEndPoint);
  try {
    NetworkStream networkStream = tcpClient.GetStream();
    StreamReader reader = new StreamReader(networkStream);
    StreamWriter writer = new StreamWriter(networkStream);
    writer.AutoFlush = true;
    while (true) {
      string request = await reader.ReadLineAsync();
      if (request != null) {
        Console.WriteLine("Received service request: " + request);
        string response = Response(request);
        Console.WriteLine("Computed response is: " + response + "\n");
        await writer.WriteLineAsync(response);
      }
      else
        break; // Client closed connection
    }
    tcpClient.Close();
  }
  catch (Exception ex) {
    Console.WriteLine(ex.Message);
    if (tcpClient.Connected)
      tcpClient.Close();
  }
}
private static string Response(string request)
{
  string[] pairs = request.Split('&');
  string methodName = pairs[0].Split('=')[1];
  string valueString = pairs[1].Split('=')[1];
  string[] values = valueString.Split(' ');
  double[] vals = new double[values.Length];
  for (int i = 0; i < values.Length; ++i)
    vals[i] = double.Parse(values[i]);
  string response = "";
  if (methodName == "average") response += Average(vals);
  else if (methodName == "minimum") response += Minimum(vals);
  else response += "BAD methodName: " + methodName;
  int delay = ((int)vals[0]) * 1000; // Dummy delay
  System.Threading.Thread.Sleep(delay);
  return response;
}

而不是 Windows 通信基礎 (WCF) 使用低級通訊端的優點之一是您可以輕鬆地插入診斷 WriteLine 語句你選擇的任何地點。 在演示中,我替換 clientEndPoint 出於安全原因的虛擬 IP 位址值 123.45.678.999。

方法過程中的三個關鍵行是:

string request = await reader.ReadLineAsync();
...
string response = Response(request);
...
await writer.WriteLineAsync(response);

您可以解釋的第一個語句,意思是"中讀取一行資料請求的非同步,允許其他語句,如果有必要執行"一旦獲得請求字串,它被傳遞到回應説明器。 然後回應返回到請求用戶端以非同步方式發送。

伺服器使用讀取請求,寫入回應週期。 這很簡單,但有的您應該知道的幾個注意事項。 如果伺服器讀取沒有寫作,它不能檢測到半開放情況。 如果伺服器寫入不讀 (例如,回應與大量的資料) 的情況下,它可以在用戶端與鎖死。 讀寫設計是可接受的簡單的內部服務,但不應該用於關鍵或面向公眾的服務。

回應方法接受請求的字串,分析請求並計算回應字串。 同時實力和弱點的基於通訊端是服務的您必須手工創建某種形式的自訂協定。 在這種情況下,假定的請求看起來像:

method=average&data=1.1 2.2 3.3&eor

換句話說,期望該文本服務"方法 ="後面的字串"平均"或"最小,"然後 & 字元 ("&") 跟隨由文本"的資料 ="。 實際輸入的資料必須以空格分隔的表單中。 請求被終止的"&"後, 跟"提高採收率,"站立為結束的請求。 基於通訊端的服務相比,WCF 的一個缺點是複雜的參數類序列化可以有點棘手有時。

在此演示的示例中,服務回應很簡單,只是平均或最小值的數值陣列的字串表示形式。 在許多自訂用戶端-伺服器情況下,你得設計一些協定的服務回應。 例如,而不是只是作為"4.00"發送一個回應,可能想要發送的回應作為"平均 = 4.00."

過程方法使用相對較粗的方法關閉連接,如果發生異常。 替代方法是使用 C# 使用的語句 (將自動關閉任何連接) 和刪除顯式調用方法關閉。

平均和最低限度的説明器方法的定義如下:

private static double Average(double[] vals)
{
  double sum = 0.0;
  for (int i = 0; i < vals.Length; ++i)
    sum += vals[i];
  return sum / vals.Length;
}
private static double Minimum(double[] vals)
{
  double min = vals[0]; ;
  for (int i = 0; i < vals.Length; ++i)
    if (vals[i] < min) min = vals[i];
  return min;
}

在大多數情況下,如果您使用的程式結構類似于演示服務,您的説明器方法在此時會連接到一些資料來源和獲取一些資料。 低級服務的一個優點是你有更好地控制您的資料存取方法。 例如,如果您從 SQL 獲取資料,你可以使用經典ADO.NET,Entity Framework或任何其他的資料存取方法。

一個低級的一個缺點是方法的你必須顯式確定如何在您的系統中處理錯誤。 在這裡,如果演示服務不能令人滿意地解析請求字串,而不是返回有效的回應 (作為一個字串),該服務返回一條錯誤訊息。 根據我的經驗,有很少的一般原則作為依據。 每個服務所需的自訂錯誤處理。

通知的回應方法具有一個虛擬的延遲:

int delay = ((int)vals[0]) * 1000;
System.Threading.Thread.Sleep(delay);

此回應延遲,任意基於請求,第一次的數值被插入了慢下來的服務,以便 WinForm 和 Web 應用程式用戶端可以在等待回應時表明 UI 回應。

WinForm 應用程式演示用戶端

若要創建中所示的 WinForm 用戶端圖 1,我發起Visual Studio2012年和創建新的 C# WinForm 應用程式命名為 DemoFormClient。 請注意預設情況下,Visual Studio模組化一個 WinForm 應用程式分為幾個 UI 代碼與邏輯代碼分開的檔。 伴隨著這篇文章的代碼下載,我重構模組化Visual Studio代碼到單個原始程式碼檔。 可以通過發射Visual Studio命令外殼程式 (其中知道,C# 編譯器在哪裡),和執行命令來編譯應用程式:csc.exe /target:winexe DemoFormClient.cs。

我使用Visual Studio設計工具,添加一個下拉式方塊控制項、 一個 TextBox 控制項、 兩個按鈕控制項和清單方塊控制項,以及四個標籤控制項。 為下拉式方塊控制項,添加控制項的項集合屬性字串"平均"和"最小"。 我分別改為發送非同步和 Say Hello,文本和屬性的 button1 button2。 然後,在設計檢視中,我按兩下 button1 和 button2 控制項以註冊其事件處理常式。 我進行編輯,按一下處理常式,如中所示圖 5

圖 5 WinForm 演示用戶端按鈕按一下事件處理常式

private async void button1_Click(object sender, EventArgs e)
{
  try {
    string server = "mymachine.
network.microsoft.com";
    int port = 50000;
    string method = (string)comboBox1.SelectedItem;
    string data = textBox1.Text;
    Task<string> tsResponse = 
      SendRequest(server, port, method, data);
    listBox1.Items.Add("Sent request, waiting for response");
    await tsResponse;
    double dResponse = double.Parse(tsResponse.Result);
    listBox1.Items.Add("Received response: " +
     dResponse.ToString("F2"));
  }
  catch (Exception ex) {
    listBox1.Items.Add(ex.Message);
  }
}
private void button2_Click(object sender, EventArgs e)
{
  listBox1.Items.Add("Hello");
}

通知的 button1 控制項的 click 處理常式簽名更改為包含 async 修飾符。 該處理常式設置了一個硬編碼伺服器的機器名作為一個字串和埠號。 使用基於通訊端的低級服務時,有沒有自動探索機制,所以用戶端必須能夠訪問的伺服器的名稱或 IP 位址和埠資訊。

關鍵的幾行代碼是:

Task<string> tsResponse = SendRequest(server, port, method, data);
// Perform some actions here if necessary
await tsResponse;
double dResponse = double.Parse(tsResponse.Result);

SendRequest 是一個程式定義的非同步方法。 調用可以寬鬆地解釋為"發送非同步請求,將返回一個字串,並在完成時繼續執行該語句在 '等待 tsResponse,' 以後發生"。這允許應用程式在等待回應的同時執行其他操作。 因為回應封裝在一個任務中,必須使用結果屬性提取實際的字串結果。 這種結果的字串轉換為類型雙,它可以很好地格式化為兩位小數。

另一種調用方法是:

string sResponse = await SendRequest(server, port, method, data);
double dResponse = double.Parse(sResponse);
listBox1.Items.Add("Received response: " + dResponse.ToString("F2"));

在這裡,等待關鍵字放置在行與對 SendRequest 的非同步調用。 這有點簡化了調用代碼,也允許對 Task.Result 的調用沒有回遷的返回字串。 選擇使用內聯等待調用或使用一個單獨的語句等待電話將會因情況發生變化,但作為一般法則,它是更好地避免顯式使用的任務物件結果屬性。

大部分的非同步工作執行在發送­請求方法,列在圖 6。 因為 SendRequest 是非同步它可能會更好地命名為 SendRequestAsync 或 MySendRequestAsync。

圖 6 WinForm 演示用戶端 SendRequest 方法

private static async Task<string> SendRequest(string server,
  int port, string method, string data)
{
  try {
    IPAddress ipAddress = null;
    IPHostEntry ipHostInfo = Dns.GetHostEntry(server);
    for (int i = 0; i < ipHostInfo.AddressList.Length; ++i) {
      if (ipHostInfo.AddressList[i].AddressFamily ==
        AddressFamily.InterNetwork)
      {
        ipAddress = ipHostInfo.AddressList[i];
        break;
      }
    }
    if (ipAddress == null)
      throw new Exception("No IPv4 address for server");
    TcpClient client = new TcpClient();
    await client.ConnectAsync(ipAddress, port); // Connect
    NetworkStream networkStream = client.GetStream();
    StreamWriter writer = new StreamWriter(networkStream);
    StreamReader reader = new StreamReader(networkStream);
    writer.AutoFlush = true;
    string requestData = "method=" + method + "&" + "data=" +
      data + "&eor"; // 'End-of-request'
    await writer.WriteLineAsync(requestData);
    string response = await reader.ReadLineAsync();
    client.Close();
    return response;
  }
  catch (Exception ex) {
    return ex.Message;
  }
}

SendRequest 接受表示伺服器名稱的字串,並開始通過那名稱解析為 IP 位址使用相同的代碼邏輯是在服務類的建構函式中使用。 更簡單的方法是伺服器的只是伺服器的傳遞的名稱:等待用戶端。ConnectAsync (伺服器、 埠)。

確定伺服器的 IP 位址後,具現化智慧通訊端物件 TcpClient 和物件的連接­非同步方法用於向伺服器發送一個連接請求。 設置完後網路 StreamWriter 物件將資料發送到伺服器並 StreamReader 物件從伺服器接收資料,請求創建了一個字串使用的格式,預期中的伺服器。 要求傳送和非同步接收和由該方法作為字串返回。

Web 應用程式演示用戶端

我創建了演示 Web 應用程式用戶端所示圖 1 兩個步驟。 首先,Visual Studio用於創建 Web 網站來承載該應用程式,然後我編碼使用記事本的 Web 應用程式。 我發起Visual Studio2012年,並創建一個新的 C# 空網站名為 DemoClient 在 HTTP://localhost/。 此設置所有必要的 IIS 水管要承載應用程式和創建與在 C:\inetpub\wwwroot\DemoClient\ 網站相關聯的物理位置。 這一進程還將創建一個基本的設定檔 web.config 檔中,其中包含.NET 框架 4.5 中允許在網站中訪問非同步功能的應用程式的資訊:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
</configuration>

下一步,我啟動記事本具有管理許可權。 當創建簡單ASP.NET應用程式,我有時寧願而不Visual Studio使用記事本,所以我可以把所有應用程式代碼放在一個.aspx 檔,而不是生成多個檔和不需要的示例代碼。 空的檔保存為 DemoWeb­Client.aspx 在 C:\inetpub\wwwroot\DemoClient。

Web 應用程式的總體結構所示圖 7

圖 7 Web 應用程式演示用戶端結構

<%@ Page Language="C#" Async="true" AutoEventWireup="true"%>
<%@ Import Namespace="System.Threading.Tasks" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Sockets" %>
<%@ Import Namespace="System.IO" %>
<script runat="server" language="C#">
  private static async Task<string> SendRequest(string server,
  private async void Button1_Click(object sender, System.EventArgs e) { .
.
}
</script>
<head>
  <title>Demo</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  <p>Enter service method:
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox></p>
  <p>Enter data:
    <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox></p>
  <p><asp:Button Text="Send Request" id="Button1"
    runat="server" OnClick="Button1_Click"> </asp:Button> </p>
  <p>Response:
    <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox></p>
  <p>Dummy responsive control:
    <asp:TextBox ID="TextBox4" runat="server"></asp:TextBox></p>
  </div>
  </form>
</body>
</html>

在頁的頂部添加導入語句帶相關的.NET 命名空間的範圍及包括非同步頁指令 = true 屬性。

該 C# 腳本區域包含兩個方法,SendRequest 和 Button1_Click。 應用程式頁身體有兩個 TextBox 控制項和一個按鈕控制項用於輸入、 輸出 TextBox 控制項的服務回應和道具、 未使用 TextBox 控制項以顯示 UI 回應應用程式等待該服務以回應請求的同時舉行。

Web 應用程式的 SendRequest 方法的代碼是完全相同,在 WinForm 應用程式的發送代碼­請求。 Web 應用程式的 Button1_Click 處理常式的代碼僅稍有不同從 WinForm 的 button1_Click 處理常式,以適應不同的 UI:

try {
  string server = "mymachine.
network.microsoft.com";
  int port = 50000;
  string method = TextBox1.Text;
  string data = TextBox2.Text;
  string sResponse = await SendRequest(server, port, method, data);
  double dResponse = double.Parse(sResponse);
  TextBox3.Text = dResponse.ToString("F2");
}
catch (Exception ex) {
  TextBox3.Text = ex.Message;
}

 

即使 Web 應用程式的代碼基本上是 WinForm 應用程式的代碼相同,調用機制是相當大的差別。當使用者請求使用 WinForm,WinForm 問題直接對服務調用和服務直接回應 WinForm。當使用者從 Web 應用程式的請求時,Web 應用程式將請求資訊發送到的 Web 服務器的承載應用程式、 Web 服務器調用到服務、 服務回應的 Web 服務器,Web 服務器構造一個包含回應的回應頁和回應頁發送回用戶端瀏覽器。

結論

因此,當應考慮使用非同步 TCP 通訊端,而不 WCF?大約 10 年前之前創建 WCF 和它的前任技術ASP.NETWeb 服務,, 如果您想要創建一個用戶端-伺服器系統,使用通訊端往往是最合乎邏輯的選項。WCF 的介紹是一大進步,但因為 WCF 的設計是為了處理的方案的龐大數目,使用它進行簡單的用戶端-伺服器系統可能會在某些情況下矯枉過正。雖然最新版本的 WCF 很容易配置比以前的版本,它仍能難與 WCF 工作。

為的情況下,用戶端和伺服器在不同網路上,使安全的主要考慮因素,我始終使用 WCF。但對於很多用戶端-伺服器系統用戶端和伺服器位於一個安全的商業網路,我通常更喜歡使用 TCP 通訊端。

執行用戶端-伺服器系統的相對較新方法是基於 HTTP 的服務與非同步方法的ASP.NETSignalR 庫結合使用ASP.NETWeb API 框架。這種方法,在許多情況下,比使用 WCF 實現更為簡單和避免了許多涉及與一個通訊端方法的低級細節。

Dr. James McCaffrey* 為微軟在華盛頓州雷德蒙德的研究工作 他曾在幾個 Microsoft 產品,包括互聯網資源管理器和 Bing。他可以在達成 jammc@microsoft.com。*

由於以下的技術專家,他們的意見,本文的審閱:皮亞利 · 喬杜裡 (MS 研究),Stephen Cleary(顧問),Adam埃弗索爾 (MS 研究) 琳權力 (MS 研究) 和StephenToub (Microsoft)