.NET での非同期パターンの簡単な履歴:
- .NET Framework 1.0 では、 IAsyncResult パターン (非同期プログラミング モデル (APM) とも呼ばれます) または
Begin/End
パターンが導入されました。 - .NET Framework 2.0 では、 イベント ベースの非同期パターン (EAP) が追加されました。
- .NET Framework 4 では、APM と EAP の両方に代わる タスク ベースの非同期パターン (TAP) が導入され、以前のパターンから移行ルーチンを簡単に構築できます。
タスクと非同期プログラミング モデル (APM)
APM から TAP へ
非同期プログラミング モデル (APM) パターンは構造化されているため、TAP 実装として APM 実装を公開するラッパーを簡単に構築できます。 .NET Framework 4 以降のバージョンには、この翻訳を提供するメソッド オーバーロードの形式のヘルパー ルーチン FromAsync 含まれています。
同期Read メソッドに対応する APM を表す、Stream クラスとそのBeginReadメソッドとEndRead メソッドについて考えてみます。
public int Read(byte[] buffer, int offset, int count)
Public Function Read(buffer As Byte(), offset As Integer,
count As Integer) As Integer
public IAsyncResult BeginRead(byte[] buffer, int offset,
int count, AsyncCallback callback,
object state)
Public Function BeginRead(buffer As Byte, offset As Integer,
count As Integer, callback As AsyncCallback,
state As Object) As IAsyncResult
public int EndRead(IAsyncResult asyncResult)
Public Function EndRead(asyncResult As IAsyncResult) As Integer
次のように、 TaskFactory<TResult>.FromAsync メソッドを使用して、この操作の TAP ラッパーを実装できます。
public static Task<int> ReadAsync(this Stream stream,
byte[] buffer, int offset,
int count)
{
if (stream == null)
throw new ArgumentNullException("stream");
return Task<int>.Factory.FromAsync(stream.BeginRead,
stream.EndRead, buffer,
offset, count, null);
}
<Extension()>
Public Function ReadAsync(strm As Stream,
buffer As Byte(), offset As Integer,
count As Integer) As Task(Of Integer)
If strm Is Nothing Then
Throw New ArgumentNullException("stream")
End If
Return Task(Of Integer).Factory.FromAsync(AddressOf strm.BeginRead,
AddressOf strm.EndRead, buffer,
offset, count, Nothing)
End Function
この実装は次のようになります。
public static Task<int> ReadAsync(this Stream stream,
byte [] buffer, int offset,
int count)
{
if (stream == null)
throw new ArgumentNullException("stream");
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, iar =>
{
try {
tcs.TrySetResult(stream.EndRead(iar));
}
catch(OperationCanceledException) {
tcs.TrySetCanceled();
}
catch(Exception exc) {
tcs.TrySetException(exc);
}
}, null);
return tcs.Task;
}
<Extension()>
Public Function ReadAsync(stream As Stream, buffer As Byte(), _
offset As Integer, count As Integer) _
As Task(Of Integer)
If stream Is Nothing Then
Throw New ArgumentNullException("stream")
End If
Dim tcs As New TaskCompletionSource(Of Integer)()
stream.BeginRead(buffer, offset, count,
Sub(iar)
Try
tcs.TrySetResult(stream.EndRead(iar))
Catch e As OperationCanceledException
tcs.TrySetCanceled()
Catch e As Exception
tcs.TrySetException(e)
End Try
End Sub, Nothing)
Return tcs.Task
End Function
TAP から APM へ
既存のインフラストラクチャで APM パターンが必要な場合は、TAP 実装を使用し、APM 実装が想定されている場所で使用することもできます。 タスクは構成でき、 Task クラスは IAsyncResultを実装するため、簡単なヘルパー関数を使用してこれを行うことができます。 次のコードでは、 Task<TResult> クラスの拡張機能を使用しますが、非ジェネリック タスクにはほぼ同じ関数を使用できます。
public static IAsyncResult AsApm<T>(this Task<T> task,
AsyncCallback callback,
object state)
{
if (task == null)
throw new ArgumentNullException("task");
var tcs = new TaskCompletionSource<T>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(t.Result);
if (callback != null)
callback(tcs.Task);
}, TaskScheduler.Default);
return tcs.Task;
}
<Extension()>
Public Function AsApm(Of T)(task As Task(Of T),
callback As AsyncCallback,
state As Object) As IAsyncResult
If task Is Nothing Then
Throw New ArgumentNullException("task")
End If
Dim tcs As New TaskCompletionSource(Of T)(state)
task.ContinueWith(Sub(antecedent)
If antecedent.IsFaulted Then
tcs.TrySetException(antecedent.Exception.InnerExceptions)
ElseIf antecedent.IsCanceled Then
tcs.TrySetCanceled()
Else
tcs.TrySetResult(antecedent.Result)
End If
If callback IsNot Nothing Then
callback(tcs.Task)
End If
End Sub, TaskScheduler.Default)
Return tcs.Task
End Function
次に、次の TAP 実装がある場合を考えてみましょう。
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
次の APM 実装を提供する必要があります。
public IAsyncResult BeginDownloadString(Uri url,
AsyncCallback callback,
object state)
Public Function BeginDownloadString(url As Uri,
callback As AsyncCallback,
state As Object) As IAsyncResult
public string EndDownloadString(IAsyncResult asyncResult)
Public Function EndDownloadString(asyncResult As IAsyncResult) As String
次の例では、APM への 1 つの移行を示します。
public IAsyncResult BeginDownloadString(Uri url,
AsyncCallback callback,
object state)
{
return DownloadStringAsync(url).AsApm(callback, state);
}
public string EndDownloadString(IAsyncResult asyncResult)
{
return ((Task<string>)asyncResult).Result;
}
Public Function BeginDownloadString(url As Uri,
callback As AsyncCallback,
state As Object) As IAsyncResult
Return DownloadStringAsync(url).AsApm(callback, state)
End Function
Public Function EndDownloadString(asyncResult As IAsyncResult) As String
Return CType(asyncResult, Task(Of String)).Result
End Function
タスクとイベント ベースの非同期パターン (EAP)
イベント ベースの非同期パターン (EAP) 実装のラップは、APM パターンをラップするよりも複雑です。EAP パターンは APM パターンよりもバリエーションが多く、構造が少ないためです。 示すために、次のコードは DownloadStringAsync
メソッドをラップします。
DownloadStringAsync
は URI を受け取り、ダウンロード中に DownloadProgressChanged
イベントを発生させ、進行状況に関する複数の統計情報を報告し、完了時に DownloadStringCompleted
イベントを発生させます。 最終的な結果は、指定された URI のページの内容を含む文字列です。
public static Task<string> DownloadStringAsync(Uri url)
{
var tcs = new TaskCompletionSource<string>();
var wc = new WebClient();
wc.DownloadStringCompleted += (s,e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
wc.DownloadStringAsync(url);
return tcs.Task;
}
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
Dim tcs As New TaskCompletionSource(Of String)()
Dim wc As New WebClient()
AddHandler wc.DownloadStringCompleted, Sub(s, e)
If e.Error IsNot Nothing Then
tcs.TrySetException(e.Error)
ElseIf e.Cancelled Then
tcs.TrySetCanceled()
Else
tcs.TrySetResult(e.Result)
End If
End Sub
wc.DownloadStringAsync(url)
Return tcs.Task
End Function
タスクと待機ハンドル
待機ハンドルから TAP へ
待機ハンドルは非同期パターンを実装しませんが、上級開発者は、待機ハンドルが設定されているときに、非同期通知に WaitHandle クラスと ThreadPool.RegisterWaitForSingleObject メソッドを使用できます。 RegisterWaitForSingleObject メソッドをラップして、待機ハンドルの同期待機に代わるタスク ベースの代替手段を有効にすることができます。
public static Task WaitOneAsync(this WaitHandle waitHandle)
{
if (waitHandle == null)
throw new ArgumentNullException("waitHandle");
var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle,
delegate { tcs.TrySetResult(true); }, null, -1, true);
var t = tcs.Task;
t.ContinueWith( (antecedent) => rwh.Unregister(null));
return t;
}
<Extension()>
Public Function WaitOneAsync(waitHandle As WaitHandle) As Task
If waitHandle Is Nothing Then
Throw New ArgumentNullException("waitHandle")
End If
Dim tcs As New TaskCompletionSource(Of Boolean)()
Dim rwh As RegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitHandle,
Sub(state, timedOut)
tcs.TrySetResult(True)
End Sub, Nothing, -1, True)
Dim t = tcs.Task
t.ContinueWith(Sub(antecedent)
rwh.Unregister(Nothing)
End Sub)
Return t
End Function
このメソッドを使用すると、非同期メソッドで既存の WaitHandle 実装を使用できます。 たとえば、特定の時点で実行されている非同期操作の数を調整する場合は、セマフォ ( System.Threading.SemaphoreSlim オブジェクト) を利用できます。 セマフォの数を N に初期化し、操作を実行するたびにセマフォを待機し、操作が完了したらセマフォを解放することで、同時に実行される操作の数を N に調整できます。
static int N = 3;
static SemaphoreSlim m_throttle = new SemaphoreSlim(N, N);
static async Task DoOperation()
{
await m_throttle.WaitAsync();
// do work
m_throttle.Release();
}
Shared N As Integer = 3
Shared m_throttle As New SemaphoreSlim(N, N)
Shared Async Function DoOperation() As Task
Await m_throttle.WaitAsync()
' Do work.
m_throttle.Release()
End Function
また、待機ハンドルに依存せず、代わりにタスクで完全に機能する非同期セマフォを構築することもできます。 これを行うには、「 タスク ベースの非同期パターンの使用 」で説明されているような手法を使用して、 Task上にデータ構造を構築できます。
TAP から待機ハンドルへ
前述のように、Task クラスはIAsyncResultを実装し、その実装では、Taskの完了時に設定される待機ハンドルを返すIAsyncResult.AsyncWaitHandle プロパティを公開します。 次のように、TaskのWaitHandleを取得できます。
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle
こちらも参照ください
.NET