비동기 반환 형식(C#)Async Return Types (C#)

비동기 메서드의 반환 형식은 다음과 같을 수 있습니다.Async methods can have the following return types:

  • Task<TResult> - 값을 반환하는 비동기 메서드의 경우Task<TResult>, for an async method that returns a value.

  • Task - 작업을 수행하지만 아무 값도 반환하지 않는 비동기 메서드의 경우Task, for an async method that performs an operation but returns no value.

  • void - 이벤트 처리기의 경우void, for an event handler.

  • C# 7.0부터 액세스 가능한 GetAwaiter 메서드가 있는 모든 형식.Starting with C# 7.0, any type that has an accessible GetAwaiter method. GetAwaiter 메서드에서 반환된 개체는 System.Runtime.CompilerServices.ICriticalNotifyCompletion 인터페이스를 구현해야 합니다.The object returned by the GetAwaiter method must implement the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface.

비동기 메서드에 대한 자세한 내용은 async 및 await를 사용한 비동기 프로그래밍(C#)을 참조하세요.For more information about async methods, see Asynchronous Programming with async and await (C#).

다음 섹션 중 하나에서 각 반환 형식을 살펴보고 세 가지 형식 모두를 사용하는 전체 예제는 이 항목의 끝에 나와 있습니다.Each return type is examined in one of the following sections, and you can find a full example that uses all three types at the end of the topic.

작업<TResult> 반환 형식Task<TResult> Return Type

Task<TResult> 반환 형식은 피연산자의 형식이 TResultReturn(C#) 문이 포함된 비동기 메서드에 사용합니다.The Task<TResult> return type is used for an async method that contains a return (C#) statement in which the operand has type TResult.

다음 예제에서 GetLeisureHours 비동기 메서드는 정수를 반환하는 return 문을 포함합니다.In the following example, the GetLeisureHours async method contains a return statement that returns an integer. 따라서 메서드 선언은 Task<int>의 반환 형식을 지정해야 합니다.Therefore, the method declaration must specify a return type of Task<int>. FromResult 비동기 메서드는 문자열을 반환하는 작업에 대한 자리 표시자입니다.The FromResult async method is a placeholder for an operation that returns a string.

using System;
using System.Linq;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      Console.WriteLine(ShowTodaysInfo().Result);
   }

   private static async Task<string> ShowTodaysInfo()
   {
      string ret = $"Today is {DateTime.Today:D}\n" +
                   "Today's hours of leisure: " +
                   $"{await GetLeisureHours()}";
      return ret;
   }

   static async Task<int> GetLeisureHours()  
   {  
       // Task.FromResult is a placeholder for actual work that returns a string.  
       var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());  
     
       // The method then can process the result in some way.  
       int leisureHours;  
       if (today.First() == 'S')  
           leisureHours = 16;  
       else  
           leisureHours = 5;  
     
       return leisureHours;  
   }  
}
// The example displays output like the following:
//       Today is Wednesday, May 24, 2017
//       Today's hours of leisure: 5
// </Snippet >

ShowTodaysInfo 메서드의 await 식 내에서 GetLeisureHours를 호출하면 await 식이 GetLeisureHours에서 반환된 작업에 저장된 정수 값(leisureHours 값)을 검색합니다.When GetLeisureHours is called from within an await expression in the ShowTodaysInfo method, the await expression retrieves the integer value (the value of leisureHours) that's stored in the task returned by the GetLeisureHours method. await 식에 대한 자세한 내용은 await를 참조하세요.For more information about await expressions, see await.

다음 코드와 같이 GetLeisureHours 호출을 await의 적용과 구분하면 이 과정을 더욱 잘 이해할 수 있습니다.You can better understand how this happens by separating the call to GetLeisureHours from the application of await, as the following code shows. 곧바로 대기 상태가 되지 않는 GetLeisureHours 메서드를 호출하면 메서드 선언에서 예상한 대로 Task<int>를 반환합니다.A call to method GetLeisureHours that isn't immediately awaited returns a Task<int>, as you would expect from the declaration of the method. 예제에서 작업이 integerTask 변수에 할당됩니다.The task is assigned to the integerTask variable in the example. integerTaskTask<TResult>이기 때문에 TResult 형식의 Result 속성을 포함합니다.Because integerTask is a Task<TResult>, it contains a Result property of type TResult. 이 경우 TResult는 정수 형식을 나타냅니다.In this case, TResult represents an integer type. awaitintegerTask에 적용되는 경우 await 식은 integerTaskResult 속성 내용으로 평가됩니다.When await is applied to integerTask, the await expression evaluates to the contents of the Result property of integerTask. 값은 ret 변수에 할당됩니다.The value is assigned to the ret variable.

중요

Result 속성은 차단 속성입니다.The Result property is a blocking property. 해당 작업이 완료되기 전에 액세스하려고 하면, 작업이 완료되고 값을 사용할 수 있을 때까지 현재 활성화된 스레드가 차단됩니다.If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. 대부분의 경우 속성에 직접 액세스하지 않고 await를 사용하여 값에 액세스해야 합니다.In most cases, you should access the value by using await instead of accessing the property directly.
앞의 예제에서는 ShowTodaysInfo 메서드가 애플리케이션 종료 전에 실행을 완료할 수 있도록 Result 속성의 값을 검색하여 주 스레드를 차단했습니다.The previous example retrieved the value of the Result property to block the main thread so that the ShowTodaysInfo method could finish execution before the application ended.

var integerTask = GetLeisureHours();

// You can do other work that does not rely on integerTask before awaiting.

string ret = $"Today is {DateTime.Today:D}\n" +
             "Today's hours of leisure: " +
             $"{await integerTask}";

Task 반환 형식Task Return Type

return 문을 포함하지 않거나 피연산자를 반환하지 않는 return 문을 포함하는 비동기 메서드의 반환 형식은 일반적으로 Task입니다.Async methods that don't contain a return statement or that contain a return statement that doesn't return an operand usually have a return type of Task. 이러한 메서드는 동기적으로 실행될 경우 void를 반환합니다.Such methods return void if they run synchronously. 비동기 메서드에 대해 Task 반환 형식을 사용하는 경우 호출된 비동기 메서드가 완료될 때까지 호출 메서드는 await 연산자를 사용하여 호출자의 완료를 일시 중단할 수 있습니다.If you use a Task return type for an async method, a calling method can use an await operator to suspend the caller's completion until the called async method has finished.

다음 예제에서 WaitAndApologize 비동기 메서드에는 return 문이 없으므로 메서드가 Task 개체를 반환합니다.In the following example, the WaitAndApologize async method doesn't contain a return statement, so the method returns a Task object. 이렇게 하면 WaitAndApologize가 대기됩니다.This enables WaitAndApologize to be awaited. Task 형식에는 반환 값이 없으므로 Result 속성이 포함되어 있지 않습니다.Note that the Task type doesn't include a Result property because it has no return value.

using System;
using System.Threading.Tasks;

public class Example
{
   public static void Main()
   {
      DisplayCurrentInfo().Wait();
   }

   static async Task DisplayCurrentInfo()
   {
      await WaitAndApologize();
      Console.WriteLine($"Today is {DateTime.Now:D}");
      Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
      Console.WriteLine("The current temperature is 76 degrees.");
   }

   static async Task WaitAndApologize()
   {
      // Task.Delay is a placeholder for actual work.  
      await Task.Delay(2000);  
      // Task.Delay delays the following line by two seconds.  
      Console.WriteLine("\nSorry for the delay. . . .\n");  
   }
}
// The example displays the following output:
//       Sorry for the delay. . . .
//       
//       Today is Wednesday, May 24, 2017
//       The current time is 15:25:16.2935649
//       The current temperature is 76 degrees.

동기 void를 반환하는 메서드에 대한 호출 문과 비슷하게 await 식 대신 await 문을 사용하여 WaitAndApologize가 대기됩니다.WaitAndApologize is awaited by using an await statement instead of an await expression, similar to the calling statement for a synchronous void-returning method. 이 경우 await 연산자를 적용하면 값이 산출되지 않습니다.The application of an await operator in this case doesn't produce a value.

앞의 Task<TResult> 예제처럼 다음 코드와 같이 WaitAndApologize 호출을 await 연산자의 적용과 구분할 수 있습니다.As in the previous Task<TResult> example, you can separate the call to WaitAndApologize from the application of an await operator, as the following code shows. 그러나 Task에는 Result 속성이 없으므로 await 연산자가 Task에 적용될 때 값이 생성되지 않습니다.However, remember that a Task doesn't have a Result property, and that no value is produced when an await operator is applied to a Task.

다음 코드에서는 WaitAndApologize 메서드 호출과 해당 메서드에서 반환하는 작업 대기를 구분합니다.The following code separates calling the WaitAndApologize method from awaiting the task that the method returns.

Task wait = WaitAndApologize();

string output = $"Today is {DateTime.Now:D}\n" + 
                $"The current time is {DateTime.Now.TimeOfDay:t}\n" +
                $"The current temperature is 76 degrees.\n";
await wait;
Console.WriteLine(output);

Void 반환 형식Void return type

void반환 형식이 필요한 비동기 이벤트 처리기에 void 반환 형식을 사용합니다.You use the void return type in asynchronous event handlers, which require a void return type. 값을 반환하지 않는 이벤트 처리기 이외의 메서드의 경우 void를 반환하는 비동기 메서드를 대기할 수 없기 때문에 Task를 대신 반환해야 합니다.For methods other than event handlers that don't return a value, you should return a Task instead, because an async method that returns void can't be awaited. 이러한 메서드의 호출자는 호출된 비동기 메서드가 마치는 것을 기다리지 않고 완료될 때까지 계속 진행할 수 있어야 하므로, 해당 호출자는 비동기 메서드가 생성하는 모든 값 또는 예외와 독립되어 있어야 합니다.Any caller of such a method must be able to continue to completion without waiting for the called async method to finish, and the caller must be independent of any values or exceptions that the async method generates.

void를 반환하는 비동기 메서드의 호출자는 메서드에서 발생한 예외를 처리할 수 없으므로 이처럼 처리되지 않은 예외는 애플리케이션을 실패하게 할 수 있습니다.The caller of a void-returning async method can't catch exceptions that are thrown from the method, and such unhandled exceptions are likely to cause your application to fail. Task 또는 Task<TResult>를 반환하는 비동기 메서드에서 예외가 발생하는 경우 해당 예외는 반환된 작업에 저장되고 작업이 대기 상태일 때 다시 발생합니다.If an exception occurs in an async method that returns a Task or Task<TResult>, the exception is stored in the returned task and is rethrown when the task is awaited. 따라서 예외를 생성할 수 있는 모든 비동기 메서드의 반환 형식이 Task 또는 Task<TResult>인지 그리고 해당 메서드 호출이 대기 상태인지 확인해야 합니다.Therefore, make sure that any async method that can produce an exception has a return type of Task or Task<TResult> and that calls to the method are awaited.

비동기 메서드에서 예외를 catch하는 방법에 대한 자세한 내용은 try-catch 문서의 비동기 메서드의 예외를 참조하세요.For more information about how to catch exceptions in async methods, see the Exceptions in Async Methods section of the try-catch topic.

다음 예제에서는 비동기 이벤트 처리기의 동작을 보여줍니다.The following example shows the behavior of an async event handler. 예제 코드에서 비동기 이벤트 처리기는 주 스레드가 완료되면 이를 알려야 합니다.Note that in the example code, an async event handler must let the main thread know when it finishes. 그런 다음, 주 스레드는 비동기 이벤트 처리기가 프로그램을 종료하기 전에 완료될 때까지 대기할 수 있습니다.Then the main thread can wait for an async event handler to complete before exiting the program.

using System;
using System.Threading.Tasks;

public class NaiveButton
{
    public event EventHandler Clicked;

    public void Click()
    {
        Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
        Clicked?.Invoke(this, EventArgs.Empty);
        Console.WriteLine("All listeners are notified.");
    }
}

public class AsyncVoidExample
{
    static TaskCompletionSource<bool> tcs;

    static async Task Main()
    {
        tcs = new TaskCompletionSource<bool>();
        var secondHandlerFinished = tcs.Task;

        var button = new NaiveButton();
        button.Clicked += Button_Clicked_1;
        button.Clicked += Button_Clicked_2_Async;
        button.Clicked += Button_Clicked_3;

        Console.WriteLine("About to click a button...");
        button.Click();
        Console.WriteLine("Button's Click method returned.");

        await secondHandlerFinished;
    }

    private static void Button_Clicked_1(object sender, EventArgs e)
    {
        Console.WriteLine("   Handler 1 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 1 is done.");
    }

    private static async void Button_Clicked_2_Async(object sender, EventArgs e)
    {
        Console.WriteLine("   Handler 2 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 2 is about to go async...");
        await Task.Delay(500);
        Console.WriteLine("   Handler 2 is done.");
        tcs.SetResult(true);
    }

    private static void Button_Clicked_3(object sender, EventArgs e)
    {
        Console.WriteLine("   Handler 3 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 3 is done.");
    }
}

// Expected output:
// About to click a button...
// Somebody has clicked a button. Let's raise the event...
//    Handler 1 is starting...
//    Handler 1 is done.
//    Handler 2 is starting...
//    Handler 2 is about to go async...
//    Handler 3 is starting...
//    Handler 3 is done.
// All listeners are notified.
// Button's Click method returned.
//    Handler 2 is done.

일반화된 비동기 반환 형식 및 ValueTask<TResult>Generalized async return types and ValueTask<TResult>

C# 7.0부터 비동기 메서드는 액세스 가능한 GetAwaiter 메서드가 있는 모든 형식을 반환할 수 있습니다.Starting with C# 7.0, an async method can return any type that has an accessible GetAwaiter method.

TaskTask<TResult>는 참조 형식이므로 특히 타이트 루프에서 할당이 발생하는 경우 성능이 중요한 경로의 메모리 할당으로 인해 성능이 저하될 수 있습니다.Because Task and Task<TResult> are reference types, memory allocation in performance-critical paths, particularly when allocations occur in tight loops, can adversely affect performance. 일반화된 반환 형식이 지원되면 추가 메모리 할당을 방지하기 위해 참조 형식 대신 간단한 값 형식을 반환할 수 있습니다.Support for generalized return types means that you can return a lightweight value type instead of a reference type to avoid additional memory allocations.

.NET에서는 System.Threading.Tasks.ValueTask<TResult> 구조체를 일반화된 작업 반환 값의 간단한 구현으로 제공합니다..NET provides the System.Threading.Tasks.ValueTask<TResult> structure as a lightweight implementation of a generalized task-returning value. System.Threading.Tasks.ValueTask<TResult> 형식을 사용하려면 System.Threading.Tasks.Extensions NuGet 패키지를 프로젝트에 추가해야 합니다.To use the System.Threading.Tasks.ValueTask<TResult> type, you must add the System.Threading.Tasks.Extensions NuGet package to your project. 다음 예제에서는 ValueTask<TResult> 구조체를 사용하여 두 주사위 굴리기 값을 검색합니다.The following example uses the ValueTask<TResult> structure to retrieve the value of two dice rolls.

using System;
using System.Threading.Tasks;

class Program
{
   static Random rnd;
   
   static void Main()
   {
      Console.WriteLine($"You rolled {GetDiceRoll().Result}");
   }
   
   private static async ValueTask<int> GetDiceRoll()
   {
      Console.WriteLine("...Shaking the dice...");
      int roll1 = await Roll();
      int roll2 = await Roll();
      return roll1 + roll2; 
   } 
   
   private static async ValueTask<int> Roll()
   {
      if (rnd == null)
         rnd = new Random();
      
      await Task.Delay(500);
      int diceRoll = rnd.Next(1, 7);
      return diceRoll;
   } 
}
// The example displays output like the following:
//       ...Shaking the dice...
//       You rolled 8

참고 항목See also