다음을 통해 공유


다른 비동기 패턴 및 형식과의 Interop

.NET에서 비동기 패턴의 간단한 기록:

작업 및 APM(비동기 프로그래밍 모델)

APM에서 TAP로

APM(비동기 프로그래밍 모델) 패턴은 구조적이므로 APM 구현을 TAP 구현으로 공개하는 래퍼를 쉽게 빌드할 수 있습니다. .NET Framework 4 이상 버전에는 이 변환을 제공하는 도우미 루틴이 FromAsync 메서드 오버로드의 형식으로 포함됩니다.

동기 Stream 메서드에 해당하는 APM 항목을 나타내는 BeginRead 클래스와 해당 EndReadRead 메서드를 고려합니다.

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으로 마이그레이션을 보여 줍니다.

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

대기 핸들에 의존하지 않고 완전히 작업으로 작동하는 비동기 세마포를 빌드할 수도 있습니다. 이렇게 하려면 Consuming the Task-based Asynchronous Pattern 에 설명된 Task이 추가되었습니다.

TAP에서 대기 핸들로

앞에서 설명한 대로 Task 클래스는 IAsyncResult를 구현하고, 해당 구현에서 IAsyncResult.AsyncWaitHandle 가 완료될 때 설정되는 대기 핸들을 반환하는 Task 속성을 노출합니다. 다음과 같이 WaitHandle 에 대한 Task 을 가져올 수 있습니다.

WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle

참고 항목