Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los métodos asincrónicos pueden tener los siguientes tipos de valor devuelto:
- Task, para un método asincrónico que realiza una operación, pero no devuelve ningún valor.
- Task<TResult>, para un método asincrónico que devuelve un valor.
-
void
, para un controlador de eventos. - Cualquier tipo que tenga un método
GetAwaiter
accesible. El objeto devuelto por el métodoGetAwaiter
debe implementar la interfaz System.Runtime.CompilerServices.ICriticalNotifyCompletion. - IAsyncEnumerable<T>, para un método asíncrono que devuelve un flujo asíncrono .
Para obtener más información sobre los métodos asincrónicos, consulte Programación asincrónica con async y await (C#).
Existen otros tipos que también son específicos de las cargas de trabajo de Windows:
- DispatcherOperation, para las operaciones asincrónicas limitadas a Windows.
- IAsyncAction, para acciones asincrónicas en aplicaciones para la Plataforma universal de Windows (UWP) que no devuelven un valor.
- IAsyncActionWithProgress<TProgress>, para las acciones asincrónicas en aplicaciones para UWP que notifican el progreso, pero no devuelven un valor.
- IAsyncOperation<TResult>, para las operaciones asincrónicas en aplicaciones para UWP que devuelven un valor.
- IAsyncOperationWithProgress<TResult,TProgress>, para las operaciones asincrónicas en aplicaciones para UWP que notifican el progreso y devuelven un valor.
Tipo de valor devuelto Task
Los métodos asincrónicos que no contienen una instrucción return
o que contienen una instrucción return
que no devuelve un operando normalmente tienen un tipo de valor devuelto de Task. Estos métodos devuelven void
si se ejecutan sincrónicamente. 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.
En el ejemplo siguiente, el método WaitAndApologizeAsync
no contiene una instrucción return
, por lo que el método devuelve un objeto Task. La devolución de Task
permite que se espere a WaitAndApologizeAsync
. El tipo Task no incluye una propiedad Result
porque no tiene ningún valor devuelto.
public static async Task DisplayCurrentInfoAsync()
{
await WaitAndApologizeAsync();
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 WaitAndApologizeAsync()
{
await Task.Delay(2000);
Console.WriteLine("Sorry for the delay...\n");
}
// Example output:
// Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.
Se espera a WaitAndApologizeAsync
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. En este caso, la aplicación de un operador await no genera un valor. Cuando el operando derecho de un await
es un Task<TResult>, la expresión await
genera un resultado de T
. Cuando el operando derecho de await
es Task, await
y su operando son una instrucción.
Puede separar la llamada a WaitAndApologizeAsync
desde la aplicación de un operador await, como muestra el código siguiente. Sin embargo, recuerde que un Task
no tiene una propiedad Result
y que no se genera ningún valor cuando se aplica un operador await a un Task
.
El código siguiente separa la llamada del método WaitAndApologizeAsync
de la espera de la tarea que el método devuelve.
Task waitAndApologizeTask = WaitAndApologizeAsync();
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 waitAndApologizeTask;
Console.WriteLine(output);
Tipo de valor devuelto Task<TResult>
El tipo de valor devuelto Task<TResult> se usa para un método asincrónico que contiene una instrucción return en la que el operando es TResult
.
En el ejemplo siguiente, el método GetLeisureHoursAsync
contiene una instrucción return
que devuelve un entero. La declaración de método debe especificar un tipo de valor devuelto de Task<int>
. El método asincrónico FromResult es un marcador de posición para una operación que devuelve un DayOfWeek.
public static async Task ShowTodaysInfoAsync()
{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";
Console.WriteLine(message);
}
static async Task<int> GetLeisureHoursAsync()
{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);
int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;
return leisureHours;
}
// Example output:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5
Cuando se llama a GetLeisureHoursAsync
desde dentro de una expresión await en el método ShowTodaysInfo
, la expresión await recupera el valor entero (el valor de leisureHours
) almacenado en la tarea devuelta por el método GetLeisureHours
. Para más información sobre las expresiones await, vea await.
Puede comprender mejor cómo await
recupera el resultado de un Task<T>
separando la llamada a GetLeisureHoursAsync
de la aplicación de await
, como se muestra en el código siguiente. Una llamada al método GetLeisureHoursAsync
que no se espera inmediatamente devuelve Task<int>
, como se podría esperar de la declaración del método. La tarea se asigna a la variable getLeisureHoursTask
en el ejemplo. Dado que getLeisureHoursTask
es un Task<TResult>, contiene una propiedad Result de tipo TResult
. En este caso, TResult
representa un tipo entero. Cuando await
se aplica a getLeisureHoursTask
, la expresión await se evalúa en el contenido de la propiedad Result de getLeisureHoursTask
. El valor se asigna a la variable ret
.
Importante
La propiedad Result es una propiedad de bloqueo. Si intenta acceder a ella antes de que finalice su tarea, el subproceso que está activo actualmente se bloquea hasta que se complete la tarea y el valor esté disponible. En la mayoría de los casos, se debe tener acceso al valor usando await
en lugar de tener acceso directamente a la propiedad.
En el ejemplo anterior se recuperó el valor de la propiedad Result para bloquear el subproceso principal para que el método Main
pudiera imprimir el message
en la consola antes de que finalizara la aplicación.
var getLeisureHoursTask = GetLeisureHoursAsync();
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";
Console.WriteLine(message);
Tipo de valor devuelto Void
Usas el tipo de retorno void
en controladores de eventos asincrónicos, que requieren un tipo de retorno void
. En el caso de los métodos distintos de los controladores de eventos que no devuelven un valor, debe devolver un Task en su lugar, porque no se puede esperar un método asincrónico que devuelva void
. Cualquiera que realice la llamada a este método debe continuar hasta completarse sin esperar a que finalice el método asincrónico que se haya llamado. El autor de la llamada debe ser independiente de los valores o excepciones que genere el método asincrónico.
El autor de la llamada de un método asincrónico que devuelva void no puede detectar las excepciones que inicia el método. Es probable que estas excepciones no controladas provoquen un error en la aplicación. Si un método que devuelve un Task o Task<TResult> produce una excepción, la excepción se almacena en la tarea devuelta. La excepción se vuelve a iniciar cuando se espera a la tarea. Asegúrese de que cualquier método asincrónico que puede iniciar una excepción tiene un tipo de valor devuelto de Task o Task<TResult>, y que se esperan las llamadas al método.
En el ejemplo siguiente se muestra el comportamiento de un controlador de eventos asincrónico. En el código de ejemplo, un controlador de eventos asincrónico debe informar al subproceso principal cuando termine. A continuación, el subproceso principal puede esperar a que se complete un controlador de eventos asincrónico antes de salir del programa.
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 readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();
public static async Task MultipleEventHandlersAsync()
{
Task<bool> secondHandlerFinished = s_tcs.Task;
var button = new NaiveButton();
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
Console.WriteLine("Before button.Click() is called...");
button.Click();
Console.WriteLine("After button.Click() is called...");
await secondHandlerFinished;
}
private static void OnButtonClicked1(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 1 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 1 is done.");
}
private static async void OnButtonClicked2Async(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.");
s_tcs.SetResult(true);
}
private static void OnButtonClicked3(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 3 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 3 is done.");
}
}
// Example output:
//
// Before button.Click() is called...
// 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.
// After button.Click() is called...
// Handler 2 is done.
Tipos de retorno asincrónico generalizados y ValueTask<TResult>
Un método asincrónico puede devolver cualquier tipo que tenga un método GetAwaiter
accesible que devuelva una instancia de un tipo awaiter. Además, el tipo devuelto desde el método GetAwaiter
debe tener el atributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute. Puede obtener más información en el artículo sobre los atributos de leídos por el compilador o la especificación de C# para el patrón del generador de tipos de tarea .
Esta característica es el complemento de las expresiones con await, que describe los requisitos del operando de await
. Los tipos de valor devueltos asincrónicos generalizados permiten al compilador generar métodos async
que devuelven tipos diferentes. Los tipos de retorno asincrónicos generalizados permitieron mejoras en el rendimiento de las bibliotecas de .NET. Dado que Task y Task<TResult> son tipos de referencia, la asignación de memoria en rutas de acceso críticas para el rendimiento, especialmente cuando se producen asignaciones en bucles estrechos, puede afectar negativamente al rendimiento. La compatibilidad con tipos de valor devuelto generalizados significa que puede devolver un tipo de valor ligero en lugar de un tipo de referencia para evitar más asignaciones de memoria.
.NET proporciona la estructura System.Threading.Tasks.ValueTask<TResult> como una implementación ligera de un valor de devolución de tareas generalizado. En el ejemplo siguiente se usa la estructura ValueTask<TResult> para recuperar el valor de dos tiradas de dados.
class Program
{
static readonly Random s_rnd = new Random();
static async Task Main() =>
Console.WriteLine($"You rolled {await GetDiceRollAsync()}");
static async ValueTask<int> GetDiceRollAsync()
{
Console.WriteLine("Shaking dice...");
int roll1 = await RollAsync();
int roll2 = await RollAsync();
return roll1 + roll2;
}
static async ValueTask<int> RollAsync()
{
await Task.Delay(500);
int diceRoll = s_rnd.Next(1, 7);
return diceRoll;
}
}
// Example output:
// Shaking dice...
// You rolled 8
La escritura de un tipo de valor devuelto asincrónico generalizado es un escenario avanzado y está destinado para su uso en entornos especializados. Considere la posibilidad de usar los tipos de Task
, Task<T>
y ValueTask<T>
en su lugar, que abarcan la mayoría de los escenarios para el código asincrónico.
Puede aplicar el atributo AsyncMethodBuilder
a un método asincrónico (en lugar de la declaración de tipo de valor devuelto asincrónico) para invalidar el generador de ese tipo. Normalmente, aplicaría este atributo para usar un generador diferente proporcionado en el entorno de ejecución de .NET.
Secuencias asincrónicas con IAsyncEnumerable<T>
Un método asincrónico puede devolver una secuencia asincrónica , representada por IAsyncEnumerable<T>. Una secuencia asincrónica proporciona una manera de enumerar los elementos leídos desde una secuencia cuando los elementos se generan en fragmentos con llamadas asincrónicas repetidas. En el ejemplo siguiente se muestra un método asincrónico que genera una secuencia asincrónica:
static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
string data =
@"This is a line of text.
Here is the second line of text.
And there is one more for good measure.
Wait, that was the penultimate line.";
using var readStream = new StringReader(data);
string? line = await readStream.ReadLineAsync();
while (line != null)
{
foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
yield return word;
}
line = await readStream.ReadLineAsync();
}
}
En el ejemplo anterior se leen líneas de una cadena de forma asincrónica. Una vez que se lee cada línea, el código enumera cada palabra de la cadena. Los llamadores enumerarían cada palabra mediante la instrucción await foreach
. El método espera cuando necesita leer de forma asincrónica la siguiente línea de la cadena de origen.