다른 비동기 패턴 및 형식과의 InteropInterop with Other Asynchronous Patterns and Types

.NET Framework 1.0에서는 IAsyncResult 또는 Begin/End패턴이라고도 하는 Begin/End 패턴이 도입되었습니다.The .NET Framework 1.0 introduced the IAsyncResult pattern, otherwise known as the Asynchronous Programming Model (APM), or the Begin/End pattern. .NET Framework 2.0에서는 Event-based Asynchronous Pattern (EAP)이 추가되었습니다.The .NET Framework 2.0 added the Event-based Asynchronous Pattern (EAP). .NET Framework 4부터 Task-based Asynchronous Pattern (TAP) 이 APM과 EAP를 둘 다 대체하지만 이전 패턴에서 마이그레이션 루틴을 쉽게 빌드할 수 있는 기능을 제공합니다.Starting with the .NET Framework 4, the Task-based Asynchronous Pattern (TAP) supersedes both APM and EAP, but provides the ability to easily build migration routines from the earlier patterns.

항목 내용In this topic:

작업 및 APM(비동기 프로그래밍 모델)Tasks and the Asynchronous Programming Model (APM)

APM에서 TAP로From APM to TAP

Asynchronous Programming Model (APM) 패턴은 매우 구조적이므로 APM 구현을 TAP 구현으로 노출하는 래퍼를 쉽게 빌드할 수 있습니다.Because the Asynchronous Programming Model (APM) pattern is very structured, it is quite easy to build a wrapper to expose an APM implementation as a TAP implementation. 사실상, .NET Framework 4부터 .NET Framework에 이 변환을 제공하는 도우미 루틴이 FromAsync 메서드 오버로드의 형태로 포함되었습니다.In fact, the .NET Framework, starting with .NET Framework 4, includes helper routines in the form of FromAsync method overloads to provide this translation.

동기 Stream 메서드에 해당하는 APM 항목을 나타내는 BeginRead 클래스와 해당 EndReadRead 메서드를 고려합니다.Consider the Stream class and its BeginRead and EndRead methods, which represent the APM counterpart to the synchronous Read method:

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 래퍼를 구현할 수 있습니다.You can use the TaskFactory<TResult>.FromAsync method to implement a TAP wrapper for this operation as follows:

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

구현은 다음과 유사합니다.This implementation is similar to the following:

 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으로From TAP to APM

기존 인프라에 APM 패턴이 필요한 경우 TAP 구현을 가져와 APM 구현이 필요한 곳에서 사용할 수도 있습니다.If your existing infrastructure expects the APM pattern, you'll also want to take a TAP implementation and use it where an APM implementation is expected. 작업이 구성되고 Task 클래스가 IAsyncResult를 구현하기 때문에 간단한 도우미 함수를 사용하여 이 작업을 수행합니다.Because tasks can be composed and the Task class implements IAsyncResult, you can use a straightforward helper function to do this. 다음 코드에서는 Task<TResult> 클래스 확장을 사용하지만 제네릭이 아닌 작업에 대해 거의 동일한 함수를 사용할 수 있습니다.The following code uses an extension of the Task<TResult> class, but you can use an almost identical function for non-generic tasks.

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 구현이 있는 경우를 고려해 보세요.Now, consider a case where you have the following TAP implementation:

public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)

다음 APM 구현을 제공하려고 합니다.and you want to provide this APM implementation:

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으로 마이그레이션을 보여 줍니다.The following example demonstrates one migration to 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(이벤트 기반 비동기 패턴)Tasks and the Event-based Asynchronous Pattern (EAP)

EAP 패턴에는 APM 패턴보다 더 많은 변형과 더 적은 구조가 있기 때문에 Event-based Asynchronous Pattern (EAP) 구현 래핑이 APM 패턴 래핑보다 훨씬 복잡합니다.Wrapping an Event-based Asynchronous Pattern (EAP) implementation is more involved than wrapping an APM pattern, because the EAP pattern has more variation and less structure than the APM pattern. 이를 보여 주기 위해 다음 코드에서는 DownloadStringAsync 메서드를 래핑합니다.To demonstrate, the following code wraps the DownloadStringAsync method. DownloadStringAsync 는 URI를 수락하고 진행 중인 여러 통계를 보고하기 위해 다운로드하는 동안 DownloadProgressChanged 이벤트를 발생시키며 완료되면 DownloadStringCompleted 이벤트를 발생시킵니다.DownloadStringAsync accepts a URI, raises the DownloadProgressChanged event while downloading in order to report multiple statistics on progress, and raises the DownloadStringCompleted event when it's done. 최종 결과는 지정된 URI에 있는 페이지의 내용이 포함된 문자열입니다.The final result is a string that contains the contents of the page at the specified 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

작업 및 대기 핸들Tasks and Wait Handles

대기 핸들에서 TAP로From Wait Handles to TAP

대기 핸들은 비동기 패턴을 구현하지 않지만 대기 핸들이 설정된 경우 고급 개발자는 WaitHandle 클래스 및 ThreadPool.RegisterWaitForSingleObject 메서드를 비동기 알림에 사용할 수 있습니다.Although wait handles don't implement an asynchronous pattern, advanced developers may use the WaitHandle class and the ThreadPool.RegisterWaitForSingleObject method for asynchronous notifications when a wait handle is set. RegisterWaitForSingleObject 메서드를 래핑하여 대기 핸들의 동기 대기에 대해 작업 기반 대체 항목을 사용하도록 설정할 수 있습니다.You can wrap the RegisterWaitForSingleObject method to enable a task-based alternative to any synchronous wait on a wait handle:

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 구현을 사용할 수 있습니다.With this method, you can use existing WaitHandle implementations in asynchronous methods. 예를 들어 특정 시간에 실행되는 비동기 작업 수를 제한하려는 경우 세마포( System.Threading.SemaphoreSlim 개체)를 활용할 수 있습니다.For example, if you want to throttle the number of asynchronous operations that are executing at any particular time, you can utilize a semaphore (a System.Threading.SemaphoreSlim object). 세마포 개수를 N 으로 초기화하고, 작업을 수행하려는 시간 동안 세마포에서 대기한 다음 작업이 완료되면 세마포를 해제하여 동시에 실행되는 작업 수를 N개로 제한할 수 있습니다.You can throttle to N the number of operations that run concurrently by initializing the semaphore’s count to N, waiting on the semaphore any time you want to perform an operation, and releasing the semaphore when you’re done with an operation:

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

대기 핸들에 의존하지 않고 완전히 작업으로 작동하는 비동기 세마포를 빌드할 수도 있습니다.You can also build an asynchronous semaphore that does not rely on wait handles and instead works completely with tasks. 이렇게 하려면 Consuming the Task-based Asynchronous Pattern 에 설명된 Task이 추가되었습니다.To do this, you can use techniques such as those discussed in Consuming the Task-based Asynchronous Pattern for building data structures on top of Task.

TAP에서 대기 핸들로From TAP to Wait Handles

앞에서 설명한 대로 Task 클래스는 IAsyncResult를 구현하고, 해당 구현에서 IAsyncResult.AsyncWaitHandle 가 완료될 때 설정되는 대기 핸들을 반환하는 Task 속성을 노출합니다.As previously mentioned, the Task class implements IAsyncResult, and that implementation exposes an IAsyncResult.AsyncWaitHandle property that returns a wait handle that will be set when the Task completes. 다음과 같이 WaitHandle 에 대한 Task 을 가져올 수 있습니다.You can get a WaitHandle for a Task as follows:

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

참고 항목See also