다음을 통해 공유


다른 비동기 패턴 및 형식과의 상호 운용

.NET의 비동기 패턴에 대한 간략한 기록:

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

APM에서 TAP으로

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

Stream 클래스와 BeginReadEndRead 메서드를 고려하세요. 이는 동기 Read 메서드에 해당하는 APM을 나타냅니다.

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 메서드를 대기 핸들이 설정될 때 비동기 알림을 위해 사용할 수 있습니다. 메서드를 캡슐화하여 대기 핸들에서의 모든 동기 대기를 작업 기반 접근 방식으로 대체할 수 있습니다.

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을 구현하고, 해당 구현에는 IAsyncResult.AsyncWaitHandle가 완료되면 설정될 대기 핸들을 반환하는 속성이 노출됩니다. 다음과 같이 WaitHandle를 위한 Task을 얻을 수 있습니다.

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

참고하십시오