Tipos de valor devueltos asincrónicos (C#)Async Return Types (C#)

Los métodos asincrónicos pueden tener los siguientes tipos de valor devuelto:Async methods can have the following return types:

  • Task<TResult>, para un método asincrónico que devuelve un valor.Task<TResult>, for an async method that returns a value.

  • Task, para un método asincrónico que realiza una operación pero no devuelve ningún valor.Task, for an async method that performs an operation but returns no value.

  • void, para un controlador de eventos.void, for an event handler.

  • A partir de C# 7.0, cualquier tipo que tenga un método GetAwaiter accesible.Starting with C# 7.0, any type that has an accessible GetAwaiter method. El objeto devuelto por el método GetAwaiter debe implementar la interfaz System.Runtime.CompilerServices.ICriticalNotifyCompletion.The object returned by the GetAwaiter method must implement the System.Runtime.CompilerServices.ICriticalNotifyCompletion interface.

Para más información sobre los métodos async, vea Programación asincrónica con async y await 8C#).For more information about async methods, see Asynchronous Programming with async and await (C#).

Cada tipo de valor devuelto se examina en una de las siguientes secciones, y puede encontrar un ejemplo completo que usa los tres tipos al final del tema.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.

Tipo de valor devuelto Task<TResult> Task<TResult> Return Type

El tipo de valor devuelto Task<TResult> se usa para un método asincrónico que contiene una instrucción return (C#) en la que el operando tiene el tipo 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.

En el ejemplo siguiente, el método asincrónico GetLeisureHours contiene una instrucción return que devuelve un entero.In the following example, the GetLeisureHours async method contains a return statement that returns an integer. Por tanto, la declaración del método debe tener un tipo de valor devuelto de Task<int>.Therefore, the method declaration must specify a return type of Task<int>. El método asincrónico FromResult es un marcador de posición para una operación que devuelve una cadena.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 >

Cuando se llama a GetLeisureHours desde una expresión await en el método ShowTodaysInfo, esta recupera el valor entero (el valor de leisureHours) que está almacenado en la tarea que devuelve el método 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. Para más información sobre las expresiones await, vea await.For more information about await expressions, see await.

Comprenderá mejor cómo sucede esto separando la llamada a GetLeisureHours de la aplicación de await, como se muestra en el código siguiente.You can better understand how this happens by separating the call to GetLeisureHours from the application of await, as the following code shows. Una llamada al método GetLeisureHours que no se espera inmediatamente devuelve Task<int>, como se podría esperar de la declaración del método.A call to method GetLeisureHours that isn't immediately awaited returns a Task<int>, as you would expect from the declaration of the method. La tarea se asigna a la variable integerTask en el ejemplo.The task is assigned to the integerTask variable in the example. Dado que integerTask es Task<TResult>, contiene una propiedad Result de tipo TResult.Because integerTask is a Task<TResult>, it contains a Result property of type TResult. En este caso, TResult representa un tipo entero.In this case, TResult represents an integer type. Cuando await se aplica a integerTask, la expresión await se evalúa en el contenido de la propiedad Result de integerTask.When await is applied to integerTask, the await expression evaluates to the contents of the Result property of integerTask. El valor se asigna a la variable ret.The value is assigned to the ret variable.

Importante

La propiedad Result es una propiedad de bloqueo.The Result property is a blocking property. Si se intenta acceder a ella antes de que termine su tarea, se bloquea el subproceso que está activo actualmente hasta que finaliza la tarea y el valor está disponible.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. En la mayoría de los casos, se debe tener acceso al valor usando await en lugar de tener acceso directamente a la propiedad.In most cases, you should access the value by using await instead of accessing the property directly.
En el ejemplo anterior se ha recuperado el valor de la propiedad Result para bloquear el subproceso principal de manera que el método ShowTodaysInfo pueda terminar la ejecución antes de que finalice la aplicación.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}";

Tipo de valor devuelto TaskTask Return Type

Los métodos asincrónicos que no contienen una instrucción return o que contienen una instrucción return que no devuelve un operando tienen normalmente un tipo de valor devuelto de 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. Dichos métodos devuelven void si se ejecutan de manera sincrónica.Such methods return void if they run synchronously. Si se usa un tipo de valor devuelto Task para un método asincrónico, un método de llamada puede usar un operador await para suspender la finalización del llamador hasta que finalice el método asincrónico llamado.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.

En el ejemplo siguiente, el método asincrónico WaitAndApologize no contiene una instrucción return, de manera que el método devuelve un objeto Task.In the following example, the WaitAndApologize async method doesn't contain a return statement, so the method returns a Task object. Esto permite que se espere a WaitAndApologize.This enables WaitAndApologize to be awaited. Tenga en cuenta que el tipo Task no incluye una propiedad Result porque no tiene ningún valor devuelto.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.

Se espera a WaitAndApologize mediante una instrucción await en lugar de una expresión await, similar a la instrucción de llamada para un método sincrónico que devuelve 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. En este caso, la aplicación de un operador await no genera un valor.The application of an await operator in this case doesn't produce a value.

Igual que en el ejemplo Task<TResult> anterior, puede separar la llamada a WaitAndApologize desde la aplicación de un operador await, como muestra el código siguiente.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. Pero recuerde que una Task no tiene una propiedad Result y que no se genera ningún valor cuando se aplica un operador await a una 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.

El código siguiente separa la llamada del método WaitAndApologize de la espera de la tarea que el método devuelve.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);

Tipo de valor devuelto VoidVoid return type

Usa el tipo de valor devuelto void en controladores de eventos asincrónicos, que necesitan un tipo de valor devuelto void.You use the void return type in asynchronous event handlers, which require a void return type. Para métodos que no sean controladores de eventos que no devuelven un valor, debe devolver Task en su lugar, porque no se espera a un método asincrónico que devuelva 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. Cualquier llamador de este método debe poder continuar hasta completarse sin esperar a que finalice el método asincrónico llamado y el llamador debe ser independiente de los valores o las excepciones que genera el método asincrónico.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.

El llamador de un método asincrónico que devuelve void no puede capturar las excepciones emitidas desde el método y dichas excepciones no controladas pueden provocar un error de la aplicación.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. Si se produce una excepción en un método asincrónico que devuelve Task o Task<TResult>, la excepción se almacena en la tarea devuelta y se vuelve a emitir cuando se espera la tarea.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. Por tanto, asegúrese de que cualquier método asincrónico que puede producir una excepción tiene un tipo de valor devuelto de Task o Task<TResult> y que se esperan llamadas al método.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.

Para obtener más información sobre cómo capturar excepciones en métodos asincrónicos, vea la sección Excepciones en métodos asincrónicos del tema 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.

En el ejemplo siguiente se muestra el comportamiento de un controlador de eventos asincrónicos.The following example shows the behavior of an async event handler. Tenga en cuenta que en el código de ejemplo, un controlador de eventos asincrónicos debe informar de que ha terminado al subproceso principal.Note that in the example code, an async event handler must let the main thread know when it finishes. Después, el subproceso principal puede esperar a que un controlador de eventos asincrónicos finalice antes de salir del programa.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.

Tipos de valor devueltos asincrónicos generalizados y ValueTask<TResult>Generalized async return types and ValueTask<TResult>

A partir de C# 7.0, un método asincrónico puede devolver cualquier tipo que tenga un método GetAwaiter accesible.Starting with C# 7.0, an async method can return any type that has an accessible GetAwaiter method.

Como Task y Task<TResult> son tipos de referencia, la asignación de memoria en las rutas críticas para el rendimiento, especialmente cuando las asignaciones se producen en ajustados bucles, puede afectar negativamente al rendimiento.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. La compatibilidad para los tipos de valor devuelto generalizados significa que puede devolver un tipo de valor ligero en lugar de un tipo de referencia para evitar asignaciones de memoria adicionales.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 proporciona la estructura System.Threading.Tasks.ValueTask<TResult> como una implementación ligera de un valor de devolución de tareas generalizado..NET provides the System.Threading.Tasks.ValueTask<TResult> structure as a lightweight implementation of a generalized task-returning value. Para usar el tipo System.Threading.Tasks.ValueTask<TResult>, debe agregar el paquete NuGet de System.Threading.Tasks.Extensions a su proyecto.To use the System.Threading.Tasks.ValueTask<TResult> type, you must add the System.Threading.Tasks.Extensions NuGet package to your project. En el ejemplo siguiente se usa la estructura ValueTask<TResult> para recuperar el valor de dos tiradas de dado.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

Vea tambiénSee also