SlideShare a Scribd company logo
.NET Web プログラミングにおける
非同期 I/O のすべて
日本マイクロソフト株式会社
エバンジェリスト
松崎 剛
http://blogs.msdn.com/b/tsmatsuz
2
セッション ゴール
• .NET Web 開発における非同期 IO の基本を学ぶ
発想の原点や歴史、基本メカニズム、用語などについて
• べし・べからず、Tips、などを理解
新機能にはワケがある !
~ スケーラブルな Web を作ろう ~
3
非同期のプログラミング・パターン
• EAP (Event-based Asynchronous Pattern)
• APM (Asynchronous Programming Model)
• TAP (Task-based Asynchronous Pattern)
FileStream fs;
byte[] readArray = new byte[0x1000];
. . .
fs.BeginRead(readArray, 0, readArray.Length,
new AsyncCallback(readCallback), fs);
. . .
private void readCallback(IAsyncResult ar)
{
System.IO.FileStream fs =
(System.IO.FileStream)ar.AsyncState;
int fsize = fs.EndRead(ar);
. . .
}
MyReadClass myRead = new MyReadClass();
myRead.ReadCompleted +=
new EventHandler<ReadCompletedEventArgs>(
readCompleted);
myRead.ReadAsync();
. . .
private void readCompleted(
object sender, ReadCompletedEventArgs e)
{
var res = e.Result;
. . .
}
using (StreamReader reader = File.OpenText(filename))
{
result = new char[reader.BaseStream.Length];
Task<int> t = reader.ReadAsync(result, 0, (int) reader.BaseStream.Length);
}
4
ASP.NET における非同期の変遷
ASP.NET Web フォーム ASP.NET MVC
.NET 2.0
.NET 3.5
.NET 4.5
.NET 4.0
5
プログラミング・パターンの相性
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
this.PreRenderComplete +=
new EventHandler(Page_PreRenderComplete);
AddOnPreRenderCompleteAsync(
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler(EndAsyncOperation));
}
}
IAsyncResult BeginAsyncOperation(object
sender, EventArgs e, AsyncCallback cb, object state)
{
_connection = new SqlConnection(connectstring);
_connection.Open();
_command = new SqlCommand(
"SELECT title_id, title, price FROM titles",
_connection);
return _command.BeginExecuteReader(cb, state);
}
void EndAsyncOperation(IAsyncResult ar)
{
_reader = _command.EndExecuteReader(ar);
}
protected void Page_PreRenderComplete(object
sender, EventArgs e)
{
Output.DataSource = _reader;
Output.DataBind();
}
ASP.NET Async Page (APM)
+
DB Access (APM)
6
TAP + async/await (C# 5.0)
public Task<ActionResult> Test1()
{
// Step 1
HttpClient cl = new HttpClient();
Task<HttpResponseMessage> task = cl.GetAsync(@"http://heavyweb.cloudapp.net/");
return task.ContinueWith(t =>
{
// Step 2
ViewBag.ResultData = t.Result.ToString();
return (ActionResult)View();
});
}
public async Task<ActionResult> Test1()
{
// Step 1
HttpClient cl = new HttpClient();
HttpResponseMessage result = await cl.GetAsync(@"http://heavyweb.cloudapp.net/");
// Step 2
ViewBag.ResultData = result.ToString();
return (ActionResult)View();
}
7
ASP.NET における非同期
要求 (Request)、キュー (Queue)、スレッド (Thread)
Web サーバー (IIS)
要求 (Request)
キュー (Queue)
処理中 …
処理中 …
処理中 …
スレッド (Thread)
スレッド プール
8
ASP.NET における非同期
同期のケース
お医者さん
=
スレッド (Thread)
患者さん
=
要求 (Request)
受付
=
キュー (Queue)
9
ASP.NET における非同期
同期のケース
10
ASP.NET における非同期
同期のケース
空くのを待機 . . .
11
ASP.NET における非同期
非同期のケース
12
ASP.NET における非同期
非同期のケース
13
ASP.NET における非同期
非同期のケース
14
I/O Completion Port (IOCP)
• Windows が提供する機構
• 1 つの IOCP は、1 つ以上のデバイス ハンドル (ファイル ハンドルな
ど) と関連
• キューのメカニズムを使って、非同期 IO の完了をプログラムから検知
• スレッドの状態 (待ち状態、リリース状態、など) を監視し、スレッド
の実行数を自動で制御
• IOCP でブロックされたスレッドは、いったん解放されて、LIFO の
キューに入る (1 つのスレッドが継続して処理可能。可能な限り
Context Switch を抑制)
• Win32 API を提供
• カスタムな制御が可能
• Thread Pool API を使った I/O 処理、Timer処理 (Thread Pool Timer)
で使用
15 15
16 16
public class Handler1 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
BetsHandler1 handler = new BetsHandler1();
context.AcceptWebSocketRequest(handler.Receive);
}
else
{
context.Response.StatusCode = 400; //bad request
}
}
. . .
}
public class BetsHandler1
{
public WebSocket webSocket;
public async Task Receive(
AspNetWebSocketContext context)
{
webSocket = context.WebSocket;
ArraySegment<byte> buf =
new ArraySegment<byte>(new byte[2048]);
while (true)
{
WebSocketReceiveResult res =
await webSocket.ReceiveAsync(
buf,
System.Threading.CancellationToken.None);
if (res.MessageType ==
WebSocketMessageType.Close)
{
// Close Message
connectedHandlers.Remove(this);
await webSocket.CloseOutputAsync(
. . .);
break;
}
else if (res.MessageType ==
WebSocketMessageType.Text)
{
// Text Message
. . . Some kind of process
}
}
}
. . .
}
17 17
public class Handler1 : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
BetsHandler1 handler = new BetsHandler1();
context.AcceptWebSocketRequest(handler.Receive);
}
else
{
context.Response.StatusCode = 400; //bad request
}
}
. . .
}
public class BetsHandler1
{
public WebSocket webSocket;
public Task Receive(
AspNetWebSocketContext context)
{
webSocket = context.WebSocket;
ArraySegment<byte> buf =
new ArraySegment<byte>(new byte[2048]);
while (true)
{
WebSocketReceiveResult res =
webSocket.ReceiveAsync(
buf,
System.Threading.CancellationToken.None);
if (res.MessageType ==
WebSocketMessageType.Close)
{
// Close Message
connectedHandlers.Remove(this);
webSocket.CloseOutputAsync(
. . .);
break;
}
else if (res.MessageType ==
WebSocketMessageType.Text)
{
// Text Message
. . . Some kind of process
}
}
return new TaskFactory().StartNew(() => { });
}
. . .
}
18
.NET 4.5 Web の TAP 対応 (“呼ぶ” 側)
• ASP.NET Web フォーム
• ASP.NET Web API
• WCF
• WebSocket
. . .
protected void Page_Load(object sender, EventArgs e)
{
Page.RegisterAsyncTask(new PageAsyncTask(async () =>
{
HttpClient cl = new HttpClient();
HttpResponseMessage res =
await cl.GetAsync(@“http://.../");
Label1.Text = res.ToString();
}));
}
public class Service1 : IService1
{
public async Task<string> GetDataAsync()
{
HttpClient cl = new HttpClient();
HttpResponseMessage res =
await cl.GetAsync(@“http://.../");
return res.ToString();
}
}
public class ValuesController : ApiController
{
// GET api/values
public async Task<string> Get()
{
HttpClient cl = new HttpClient();
HttpResponseMessage res =
await cl.GetAsync(@"http://.../");
return res.ToString();
}
}
context.AcceptWebSocketRequest(handler.Receive);
. . .
public async Task Receive(AspNetWebSocketContext context)
{
while (true)
{
WebSocketReceiveResult res =
await context.WebSocket.ReceiveAsync(. . .);
. . .
}
}
19
IO リソースの TAP 対応 (“呼ばれる” 側)
ファイル入出力 .NET 4.5 で TAP (async) のメソッド (ReadAsync, WriteAsync,
CopyToAsync など) を提供 (これまでは、APM のみ)
データ
ベース
ADO.NET .NET 4.5 で TAP (async) のメソッド (ReadAsync など) を提供
(これまでは、APM のみ)
Entity
Framework
Entity Framework 6 で、TAP (async) をサポート
(現在、ベータ版を提供)
ネット
ワーク
REST HttpClient のメソッドは、基本的に TAP ベース
WCF .NET 4.5 で自動生成される Service Refrence Proxy では、TAP
(async) のメソッドを提供
クラウド Windows Azure
ServiceBus
最新の WindowsAzure.ServiceBus パッケージ (NuGet) で TAP の
メソッド (NamespaceManager.QueueExistsAsync など) を提供
(APM も使用可能)
では、未対応のものはどうする ?
(例 : WCF Data Services, Windows Azure Storage など)
ますます、対応中 . . . (こうご期待!)
20
SynchronizationContext
• スレッド間の関係を管理する抽象化されたスケジューラー・オブジェクト
 ASP.NET 非同期スレッドは、Win32 メッセージ ループのように特定スレッドに紐づかない
• 1 つのスレッドに対し、必ず 1 つの SynchronizationContext が存在 (ただし、単一
のSynchronizationContext は複数スレッドで共有)
• 一部の実装 (override メソッド) を除き、具体的な実装は派生クラスに依存
 WindowsFormsSynchronizationContext
 DispatcherSynchronizationContext
 AspNetSynchronizationContext
 既定の SynchronizationContext
• これまでの非同期処理 (EAP など) において、その動作をつかさどる
• TAP では TaskScheduler を使用 (SynchronizationContext を使用する際は、
TaskScheduler.FromCurrentSynchronizationContext を明示)
21
SynchronizationContext
• 既定の Awaiter (TaskAwaiter) は、Current の SynchronizationContext を使用
(なければ TaskScheduler も参照)
 AspNetSynchronizationContext では、同期ブロックに入れるスレッドは 1 つだけ
• .NET 4 以降では、Task と相性の良い新しい AspNetSynchronizationContext を使用
 従来のものは LegacyAspNetSynchronizationContext に変更
この場合でも、Web.configの設定で新しいContextを
使用可能
<appSettings>
<add
key="aspnet:UseTaskFriendlySynchronizationContext“
value="true"/>
</appSettings>
22
混ぜるな、危険 💀
• Async (EAP, TAP, etc) と Sync の混
在プログラムは、デッドロックの原因
となる !
Task で受け取った内容を、むりやり同期
化しない (All async is beautiful !)
「扱いやすい」(理解しやすい) という理
由だけで、 Result、Wait を多用しない
(初心者にありがちなミス)
現実の開発では、追跡とデバッグが非常に
困難 (例 : 単一では動作するんだけ
ど ?、コンソール・アプリでは動くのに ?
など)
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)

More Related Content

.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)

  • 1. .NET Web プログラミングにおける 非同期 I/O のすべて 日本マイクロソフト株式会社 エバンジェリスト 松崎 剛 http://blogs.msdn.com/b/tsmatsuz
  • 2. 2 セッション ゴール • .NET Web 開発における非同期 IO の基本を学ぶ 発想の原点や歴史、基本メカニズム、用語などについて • べし・べからず、Tips、などを理解 新機能にはワケがある ! ~ スケーラブルな Web を作ろう ~
  • 3. 3 非同期のプログラミング・パターン • EAP (Event-based Asynchronous Pattern) • APM (Asynchronous Programming Model) • TAP (Task-based Asynchronous Pattern) FileStream fs; byte[] readArray = new byte[0x1000]; . . . fs.BeginRead(readArray, 0, readArray.Length, new AsyncCallback(readCallback), fs); . . . private void readCallback(IAsyncResult ar) { System.IO.FileStream fs = (System.IO.FileStream)ar.AsyncState; int fsize = fs.EndRead(ar); . . . } MyReadClass myRead = new MyReadClass(); myRead.ReadCompleted += new EventHandler<ReadCompletedEventArgs>( readCompleted); myRead.ReadAsync(); . . . private void readCompleted( object sender, ReadCompletedEventArgs e) { var res = e.Result; . . . } using (StreamReader reader = File.OpenText(filename)) { result = new char[reader.BaseStream.Length]; Task<int> t = reader.ReadAsync(result, 0, (int) reader.BaseStream.Length); }
  • 4. 4 ASP.NET における非同期の変遷 ASP.NET Web フォーム ASP.NET MVC .NET 2.0 .NET 3.5 .NET 4.5 .NET 4.0
  • 5. 5 プログラミング・パターンの相性 protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { this.PreRenderComplete += new EventHandler(Page_PreRenderComplete); AddOnPreRenderCompleteAsync( new BeginEventHandler(BeginAsyncOperation), new EndEventHandler(EndAsyncOperation)); } } IAsyncResult BeginAsyncOperation(object sender, EventArgs e, AsyncCallback cb, object state) { _connection = new SqlConnection(connectstring); _connection.Open(); _command = new SqlCommand( "SELECT title_id, title, price FROM titles", _connection); return _command.BeginExecuteReader(cb, state); } void EndAsyncOperation(IAsyncResult ar) { _reader = _command.EndExecuteReader(ar); } protected void Page_PreRenderComplete(object sender, EventArgs e) { Output.DataSource = _reader; Output.DataBind(); } ASP.NET Async Page (APM) + DB Access (APM)
  • 6. 6 TAP + async/await (C# 5.0) public Task<ActionResult> Test1() { // Step 1 HttpClient cl = new HttpClient(); Task<HttpResponseMessage> task = cl.GetAsync(@"http://heavyweb.cloudapp.net/"); return task.ContinueWith(t => { // Step 2 ViewBag.ResultData = t.Result.ToString(); return (ActionResult)View(); }); } public async Task<ActionResult> Test1() { // Step 1 HttpClient cl = new HttpClient(); HttpResponseMessage result = await cl.GetAsync(@"http://heavyweb.cloudapp.net/"); // Step 2 ViewBag.ResultData = result.ToString(); return (ActionResult)View(); }
  • 7. 7 ASP.NET における非同期 要求 (Request)、キュー (Queue)、スレッド (Thread) Web サーバー (IIS) 要求 (Request) キュー (Queue) 処理中 … 処理中 … 処理中 … スレッド (Thread) スレッド プール
  • 14. 14 I/O Completion Port (IOCP) • Windows が提供する機構 • 1 つの IOCP は、1 つ以上のデバイス ハンドル (ファイル ハンドルな ど) と関連 • キューのメカニズムを使って、非同期 IO の完了をプログラムから検知 • スレッドの状態 (待ち状態、リリース状態、など) を監視し、スレッド の実行数を自動で制御 • IOCP でブロックされたスレッドは、いったん解放されて、LIFO の キューに入る (1 つのスレッドが継続して処理可能。可能な限り Context Switch を抑制) • Win32 API を提供 • カスタムな制御が可能 • Thread Pool API を使った I/O 処理、Timer処理 (Thread Pool Timer) で使用
  • 15. 15 15
  • 16. 16 16 public class Handler1 : IHttpHandler { public void ProcessRequest(HttpContext context) { if (context.IsWebSocketRequest) { BetsHandler1 handler = new BetsHandler1(); context.AcceptWebSocketRequest(handler.Receive); } else { context.Response.StatusCode = 400; //bad request } } . . . } public class BetsHandler1 { public WebSocket webSocket; public async Task Receive( AspNetWebSocketContext context) { webSocket = context.WebSocket; ArraySegment<byte> buf = new ArraySegment<byte>(new byte[2048]); while (true) { WebSocketReceiveResult res = await webSocket.ReceiveAsync( buf, System.Threading.CancellationToken.None); if (res.MessageType == WebSocketMessageType.Close) { // Close Message connectedHandlers.Remove(this); await webSocket.CloseOutputAsync( . . .); break; } else if (res.MessageType == WebSocketMessageType.Text) { // Text Message . . . Some kind of process } } } . . . }
  • 17. 17 17 public class Handler1 : IHttpHandler { public void ProcessRequest(HttpContext context) { if (context.IsWebSocketRequest) { BetsHandler1 handler = new BetsHandler1(); context.AcceptWebSocketRequest(handler.Receive); } else { context.Response.StatusCode = 400; //bad request } } . . . } public class BetsHandler1 { public WebSocket webSocket; public Task Receive( AspNetWebSocketContext context) { webSocket = context.WebSocket; ArraySegment<byte> buf = new ArraySegment<byte>(new byte[2048]); while (true) { WebSocketReceiveResult res = webSocket.ReceiveAsync( buf, System.Threading.CancellationToken.None); if (res.MessageType == WebSocketMessageType.Close) { // Close Message connectedHandlers.Remove(this); webSocket.CloseOutputAsync( . . .); break; } else if (res.MessageType == WebSocketMessageType.Text) { // Text Message . . . Some kind of process } } return new TaskFactory().StartNew(() => { }); } . . . }
  • 18. 18 .NET 4.5 Web の TAP 対応 (“呼ぶ” 側) • ASP.NET Web フォーム • ASP.NET Web API • WCF • WebSocket . . . protected void Page_Load(object sender, EventArgs e) { Page.RegisterAsyncTask(new PageAsyncTask(async () => { HttpClient cl = new HttpClient(); HttpResponseMessage res = await cl.GetAsync(@“http://.../"); Label1.Text = res.ToString(); })); } public class Service1 : IService1 { public async Task<string> GetDataAsync() { HttpClient cl = new HttpClient(); HttpResponseMessage res = await cl.GetAsync(@“http://.../"); return res.ToString(); } } public class ValuesController : ApiController { // GET api/values public async Task<string> Get() { HttpClient cl = new HttpClient(); HttpResponseMessage res = await cl.GetAsync(@"http://.../"); return res.ToString(); } } context.AcceptWebSocketRequest(handler.Receive); . . . public async Task Receive(AspNetWebSocketContext context) { while (true) { WebSocketReceiveResult res = await context.WebSocket.ReceiveAsync(. . .); . . . } }
  • 19. 19 IO リソースの TAP 対応 (“呼ばれる” 側) ファイル入出力 .NET 4.5 で TAP (async) のメソッド (ReadAsync, WriteAsync, CopyToAsync など) を提供 (これまでは、APM のみ) データ ベース ADO.NET .NET 4.5 で TAP (async) のメソッド (ReadAsync など) を提供 (これまでは、APM のみ) Entity Framework Entity Framework 6 で、TAP (async) をサポート (現在、ベータ版を提供) ネット ワーク REST HttpClient のメソッドは、基本的に TAP ベース WCF .NET 4.5 で自動生成される Service Refrence Proxy では、TAP (async) のメソッドを提供 クラウド Windows Azure ServiceBus 最新の WindowsAzure.ServiceBus パッケージ (NuGet) で TAP の メソッド (NamespaceManager.QueueExistsAsync など) を提供 (APM も使用可能) では、未対応のものはどうする ? (例 : WCF Data Services, Windows Azure Storage など) ますます、対応中 . . . (こうご期待!)
  • 20. 20 SynchronizationContext • スレッド間の関係を管理する抽象化されたスケジューラー・オブジェクト  ASP.NET 非同期スレッドは、Win32 メッセージ ループのように特定スレッドに紐づかない • 1 つのスレッドに対し、必ず 1 つの SynchronizationContext が存在 (ただし、単一 のSynchronizationContext は複数スレッドで共有) • 一部の実装 (override メソッド) を除き、具体的な実装は派生クラスに依存  WindowsFormsSynchronizationContext  DispatcherSynchronizationContext  AspNetSynchronizationContext  既定の SynchronizationContext • これまでの非同期処理 (EAP など) において、その動作をつかさどる • TAP では TaskScheduler を使用 (SynchronizationContext を使用する際は、 TaskScheduler.FromCurrentSynchronizationContext を明示)
  • 21. 21 SynchronizationContext • 既定の Awaiter (TaskAwaiter) は、Current の SynchronizationContext を使用 (なければ TaskScheduler も参照)  AspNetSynchronizationContext では、同期ブロックに入れるスレッドは 1 つだけ • .NET 4 以降では、Task と相性の良い新しい AspNetSynchronizationContext を使用  従来のものは LegacyAspNetSynchronizationContext に変更 この場合でも、Web.configの設定で新しいContextを 使用可能 <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext“ value="true"/> </appSettings>
  • 22. 22 混ぜるな、危険 💀 • Async (EAP, TAP, etc) と Sync の混 在プログラムは、デッドロックの原因 となる ! Task で受け取った内容を、むりやり同期 化しない (All async is beautiful !) 「扱いやすい」(理解しやすい) という理 由だけで、 Result、Wait を多用しない (初心者にありがちなミス) 現実の開発では、追跡とデバッグが非常に 困難 (例 : 単一では動作するんだけ ど ?、コンソール・アプリでは動くのに ? など)