非同期の戻り値の型 (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. System.Runtime.CompilerServices.ICriticalNotifyCompletion メソッドによって返されるオブジェクトは、GetAwaiter インターフェイスを実装する必要があります。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#).

それぞれの戻り値の型は、次のセクションの 1 つで確認でき、トピックの最後で 3 種類のすべてを使用する例を参照できます。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.

Task<TResult> の戻り値の型Task<TResult> Return Type

TResult 型のオペランドを持つ return (C#) ステートメントを含む非同期メソッドには、Task<TResult> 戻り値の型が使用されます。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 >

GetLeisureHoursShowTodaysInfo メソッドの await 式の中から呼び出されると、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> であるため、Result 型の TResult プロパティが含まれています。Because integerTask is a Task<TResult>, it contains a Result property of type TResult. この場合、TResult は整数型を表します。In this case, TResult represents an integer type. awaitintegerTask に適用されると、integerTaskResult プロパティの内容が await 式の評価となります。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 プロパティは Blocking プロパティです。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 演算子を使って、呼び出された async のメソッドが終了するまで、呼び出し元の完了を中断します。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.

WaitAndApologize を待機するには、void を返す同期メソッドを呼び出す場合と同様に、await 式でなく、await ステートメントを使用します。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. ただし TaskResult プロパティを持たないこと、また 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. 値を返さないイベント ハンドラー以外のメソッドについては、Task を返す必要があります。これは、void を返す非同期メソッドを待機できないためです。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.

非同期メソッドで例外をキャッチする方法の詳細については、「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.

Task および Task<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> 構造を使用して、2 つのさいころを転がしたときの値を取得します。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