Асинхронные типы возвращаемых значений (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.

  • Начиная с версии 7.0 в языке C# поддерживаются любые типы с доступным методом 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> используется для асинхронного метода, содержащего инструкцию return (C#), в которой операнд имеет тип 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 >

При вызове GetLeisureHours из выражения await в методе ShowTodaysInfo это выражение await извлекает целочисленное значение (значение leisureHours), хранящееся в задаче, которая возвращается методом GetLeisureHours.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. Поскольку integerTask является Task<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. Если выражение await применяется к integerTask, выражение await вычисляется как содержимое свойства Result объекта integerTask.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.
В предыдущем примере извлекалось значение свойства Result для блокировки основного потока. Это позволяет закончить выполнение метода ShowTodaysInfo до того, как завершится работа приложения.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}";

Тип возвращаемого значения TaskTask 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.

WaitAndApologize вызывается и ожидается с помощью инструкции await (вместо выражения await), похожей на инструкцию вызова для синхронного метода, возвращающего значение void.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);

Тип возвращаемого значения VoidVoid 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>, необходимо добавить в проект пакет NuGet System.Threading.Tasks.Extensions.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