비동기 작업을 여러 개 시작하고 완료될 때마다 처리(C# 및 Visual Basic)
Task.WhenAny을 이용하여 여러 작업을 동시에 착수하고 작업을 시작 순서대로 처리하는 것이 아니라 완료하는 시점에 하나씩 처리할 수 있습니다.
다음 예제는 쿼리를 이용하여 작업 컬렉션을 만듭니다. 각 작업은 지정된 웹 사이트의 콘텐츠를 다운로드합니다. while 루프의 각 반복에서 WhenAny에 대한 대기된 호출은 먼저 다운로드를 완료한 순서대로 컬렉션에 있는 작업을 반환합니다. 작업이 컬렉션에서 제거되고 처리됩니다. 루프는 컬렉션에 작업이 더 이상 없을 때까지 반복됩니다.
참고
예제를 실행하려면 Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 for Windows Desktop, Visual Studio Express 2013 for Windows 또는 .NET Framework 4.5이나 4.5.1이 컴퓨터에 설치되어 있어야 합니다.
예제 다운로드
Async 샘플: 응용 프로그램 미세 조정에서 전체 WPF(Windows Presentation Foundation) 프로젝트를 다운로드하고 다음 단계를 수행합니다.
다운로드한 파일을 압축한 다음 Visual Studio를 시작합니다.
메뉴 모음에서 파일, 열기, 프로젝트/솔루션을 선택합니다.
프로젝트 열기 대화 상자에서 압축한 샘플 코드가 있는 폴더를 연 다음 AsyncFineTuningCS 또는 AsyncFineTuningVB에 대한 솔루션 파일(.sln)을 엽니다.
솔루션 탐색기에서 ProcessTasksAsTheyFinish 프로젝트에 대한 바로 가기 메뉴를 연 후 시작 프로젝트로 설정을 선택합니다.
F5 키를 선택하여 프로젝트를 실행합니다.
디버깅하지 않고 프로젝트를 실행하려면 Ctrl+F5 키를 선택합니다.
다운로드한 길이가 항상 동일한 순서로 나타나지 않도록 프로젝트를 여러 번 실행합니다.
프로젝트를 다운로드하지 않으려면 항목의 끝에서 MainWindow.xaml.vb 및 MainWindow.xaml.cs 파일을 검토할 수 있습니다.
예제 빌드
이 예제에서는 비동기 작업 하나가 완료되면 남은 비동기 작업 취소(C# 및 Visual Basic)에서 개발된 코드에 추가하고 동일한 UI를 사용합니다.
예제를 스스로 단계별로 빌드하려면 "예제 다운로드" 단원의 지침을 따르지만 시작 프로젝트로 CancelAfterOneTask를 선택하십시오. 이 항목의 변경 사항을 해당 프로젝트의 AccessTheWebAsync 메서드에 추가합니다. 변경 내용은 별표로 표시됩니다.
CancelAfterOneTask 프로젝트에는 실행하면 작업 컬렉션을 만드는 쿼리가 이미 포함되어 있습니다. TResult이 정수일 경우 다음의 코드에서 ProcessURLAsync에 대한 각 호출이 Task을 반환합니다.
Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
From url In urlList Select ProcessURLAsync(url, client, ct)
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
프로젝트의 MainWindow.xaml.vb 또는 MainWindow.xaml.cs 파일에서 AccessTheWebAsync 메서드를 다음과 같이 변경합니다.
ToArray``1 대신 Enumerable.ToList``1을 적용하여 쿼리를 실행합니다.
Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
컬렉션의 각 작업에 다음 단계를 수행하는 while 루프를 추가합니다.
WhenAny에 대한 호출을 기다려 컬렉션의 첫 번째 작업을 식별하고 다운로드를 마칩니다.
Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
컬렉션에서 작업을 제거합니다.
downloadTasks.Remove(firstFinishedTask)
downloadTasks.Remove(firstFinishedTask);
ProcessURLAsync에 대한 호출로 반환되는 firstFinishedTask를 대기합니다. firstFinishedTask 변수는 TReturn이 정수인 Task입니다. 작업이 이미 완료되었지만 다음 예제와 같이 다운로드한 웹 사이트의 길이를 검색할 때까지 기다립니다.
Dim length = Await firstFinishedTask resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website: {0}" & vbCrLf, length)
int length = await firstFinishedTask; resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
다운로드한 길이가 항상 동일한 순서로 나타나지 않도록 하려면 프로젝트를 여러 번 실행해야 합니다.
경고
소규모 작업이 포함된 문제를 해결하기 위해 예제에 설명된 것처럼 루프에 WhenAny를 사용할 수 있습니다.그러나 처리할 작업 수가 많으면 다른 방법이 더 효율적입니다.자세한 내용 및 예제를 보려면 Processing Tasks as they complete를 참조하십시오.
완성된 예제
다음 코드는 예제의 MainWindow.xaml.vb 또는 MainWindow.xaml.cs 파일의 전체 텍스트입니다. 별표는 이 예제에 추가된 요소를 표시합니다.
System.Net.Http에 대한 참조를 추가해야 합니다.
Async 샘플: 응용 프로그램 미세 조정에서 프로젝트를 다운로드할 수 있습니다.
' Add an Imports directive and a reference for System.Net.Http.
Imports System.Net.Http
' Add the following Imports directive for System.Threading.
Imports System.Threading
Class MainWindow
' Declare a System.Threading.CancellationTokenSource.
Dim cts As CancellationTokenSource
Private Async Sub startButton_Click(sender As Object, e As RoutedEventArgs)
' Instantiate the CancellationTokenSource.
cts = New CancellationTokenSource()
resultsTextBox.Clear()
Try
Await AccessTheWebAsync(cts.Token)
resultsTextBox.Text &= vbCrLf & "Downloads complete."
Catch ex As OperationCanceledException
resultsTextBox.Text &= vbCrLf & "Downloads canceled." & vbCrLf
Catch ex As Exception
resultsTextBox.Text &= vbCrLf & "Downloads failed." & vbCrLf
End Try
' Set the CancellationTokenSource to Nothing when the download is complete.
cts = Nothing
End Sub
' You can still include a Cancel button if you want to.
Private Sub cancelButton_Click(sender As Object, e As RoutedEventArgs)
If cts IsNot Nothing Then
cts.Cancel()
End If
End Sub
' Provide a parameter for the CancellationToken.
' Change the return type to Task because the method has no return statement.
Async Function AccessTheWebAsync(ct As CancellationToken) As Task
Dim client As HttpClient = New HttpClient()
' Call SetUpURLList to make a list of web addresses.
Dim urlList As List(Of String) = SetUpURLList()
' ***Create a query that, when executed, returns a collection of tasks.
Dim downloadTasksQuery As IEnumerable(Of Task(Of Integer)) =
From url In urlList Select ProcessURLAsync(url, client, ct)
' ***Use ToList to execute the query and start the download tasks.
Dim downloadTasks As List(Of Task(Of Integer)) = downloadTasksQuery.ToList()
' ***Add a loop to process the tasks one at a time until none remain.
While downloadTasks.Count > 0
' ***Identify the first task that completes.
Dim firstFinishedTask As Task(Of Integer) = Await Task.WhenAny(downloadTasks)
' ***Remove the selected task from the list so that you don't
' process it more than once.
downloadTasks.Remove(firstFinishedTask)
' ***Await the first completed task and display the results.
Dim length = Await firstFinishedTask
resultsTextBox.Text &= String.Format(vbCrLf & "Length of the downloaded website: {0}" & vbCrLf, length)
End While
End Function
' Bundle the processing steps for a website into one async method.
Async Function ProcessURLAsync(url As String, client As HttpClient, ct As CancellationToken) As Task(Of Integer)
' GetAsync returns a Task(Of HttpResponseMessage).
Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)
' Retrieve the website contents from the HttpResponseMessage.
Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()
Return urlContents.Length
End Function
' Add a method that creates a list of web addresses.
Private Function SetUpURLList() As List(Of String)
Dim urls = New List(Of String) From
{
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/en-us/library/hh290138.aspx",
"https://msdn.microsoft.com/en-us/library/hh290140.aspx",
"https://msdn.microsoft.com/en-us/library/dd470362.aspx",
"https://msdn.microsoft.com/en-us/library/aa578028.aspx",
"https://msdn.microsoft.com/en-us/library/ms404677.aspx",
"https://msdn.microsoft.com/en-us/library/ff730837.aspx"
}
Return urls
End Function
End Class
' Sample output:
' Length of the download: 226093
' Length of the download: 412588
' Length of the download: 175490
' Length of the download: 204890
' Length of the download: 158855
' Length of the download: 145790
' Length of the download: 44908
' Downloads complete.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add a using directive and a reference for System.Net.Http.
using System.Net.Http;
// Add the following using directive.
using System.Threading;
namespace ProcessTasksAsTheyFinish
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
// Instantiate the CancellationTokenSource.
cts = new CancellationTokenSource();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
private void cancelButton_Click(object sender, RoutedEventArgs e)
{
if (cts != null)
{
cts.Cancel();
}
}
async Task AccessTheWebAsync(CancellationToken ct)
{
HttpClient client = new HttpClient();
// Make a list of web addresses.
List<string> urlList = SetUpURLList();
// ***Create a query that, when executed, returns a collection of tasks.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURL(url, client, ct);
// ***Use ToList to execute the query and start the tasks.
List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
// Await the completed task.
int length = await firstFinishedTask;
resultsTextBox.Text += String.Format("\r\nLength of the download: {0}", length);
}
}
private List<string> SetUpURLList()
{
List<string> urls = new List<string>
{
"https://msdn.microsoft.com",
"https://msdn.microsoft.com/library/windows/apps/br211380.aspx",
"https://msdn.microsoft.com/en-us/library/hh290136.aspx",
"https://msdn.microsoft.com/en-us/library/dd470362.aspx",
"https://msdn.microsoft.com/en-us/library/aa578028.aspx",
"https://msdn.microsoft.com/en-us/library/ms404677.aspx",
"https://msdn.microsoft.com/en-us/library/ff730837.aspx"
};
return urls;
}
async Task<int> ProcessURL(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
}
}
// Sample Output:
// Length of the download: 226093
// Length of the download: 412588
// Length of the download: 175490
// Length of the download: 204890
// Length of the download: 158855
// Length of the download: 145790
// Length of the download: 44908
// Downloads complete.
참고 항목
참조
개념
Async 응용 프로그램 미세 조정(C# 및 Visual Basic)
Async 및 Await를 사용한 비동기 프로그래밍(C# 및 Visual Basic)