.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)
- 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);
}
- 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();
}
- 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)
で使用
- 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 を多用しない
(初心者にありがちなミス)
現実の開発では、追跡とデバッグが非常に
困難 (例 : 単一では動作するんだけ
ど ?、コンソール・アプリでは動くのに ?
など)