Respuestas:
No puede tener métodos asincrónicos con refo outparámetros.
Lucian Wischik explica por qué esto no es posible en este hilo de MSDN: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have -ref-or-out-parámetros
¿Por qué los métodos asíncronos no admiten parámetros de referencia? (¿o parámetros de referencia?) Esa es una limitación del CLR. Elegimos implementar métodos asincrónicos de manera similar a los métodos iteradores, es decir, a través del compilador transformando el método en un objeto máquina de estado. El CLR no tiene una forma segura de almacenar la dirección de un "parámetro de salida" o "parámetro de referencia" como un campo de un objeto. La única forma de admitir parámetros de salida por referencia sería si la función asincrónica se realizara mediante una reescritura CLR de bajo nivel en lugar de una reescritura del compilador. Examinamos ese enfoque, y tenía muchas posibilidades, pero en última instancia habría sido tan costoso que nunca hubiera sucedido.
Una solución alternativa típica para esta situación es hacer que el método asíncrono devuelva una Tupla. Podrías reescribir tu método como tal:
public async Task Method1()
{
var tuple = await GetDataTaskAsync();
int op = tuple.Item1;
int result = tuple.Item2;
}
public async Task<Tuple<int, int>> GetDataTaskAsync()
{
//...
return new Tuple<int, int>(1, 2);
}
Tuplealternativa. Muy útil.
Tuple. : P
No puede tener refni outparámetros en los asyncmétodos (como ya se señaló).
Esto grita un poco de modelado en los datos que se mueven:
public class Data
{
public int Op {get; set;}
public int Result {get; set;}
}
public async void Method1()
{
Data data = await GetDataTaskAsync();
// use data.Op and data.Result from here on
}
public async Task<Data> GetDataTaskAsync()
{
var returnValue = new Data();
// Fill up returnValue
return returnValue;
}
Obtiene la capacidad de reutilizar su código más fácilmente, además es mucho más legible que las variables o las tuplas.
La solución C # 7 + es usar sintaxis implícita de tupla.
private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
{
return (true, BadRequest(new OpenIdErrorResponse
{
Error = OpenIdConnectConstants.Errors.AccessDenied,
ErrorDescription = "Access token provided is not valid."
}));
}
El resultado de retorno utiliza los nombres de propiedad definidos por la firma del método. p.ej:
var foo = await TryLogin(request);
if (foo.IsSuccess)
return foo.Result;
Alex hizo un gran punto sobre la legibilidad. De manera equivalente, una función también es lo suficientemente interfaz como para definir los tipos que se devuelven y también se obtienen nombres de variables significativos.
delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
bool canGetData = true;
if (canGetData) callback(5);
return Task.FromResult(canGetData);
}
Las personas que llaman proporcionan una lambda (o una función con nombre) e intellisense ayuda copiando los nombres de las variables del delegado.
int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);
Este enfoque particular es como un método "Probar" donde myOpse establece si el resultado del método es true. De lo contrario, no te importa myOp.
Una buena característica de los outparámetros es que pueden usarse para devolver datos incluso cuando una función arroja una excepción. Creo que el equivalente más cercano a hacer esto con un asyncmétodo sería usar un nuevo objeto para contener los datos a los que tanto el asyncmétodo como la persona que llama pueden referirse. Otra forma sería pasar un delegado como se sugiere en otra respuesta .
Tenga en cuenta que ninguna de estas técnicas tendrá el tipo de aplicación del compilador que outtiene. Es decir, el compilador no requerirá que establezca el valor en el objeto compartido o llame a un delegado pasado.
Aquí hay una implementación de ejemplo usando un objeto compartido para imitar refy outpara usar con asyncmétodos y otros escenarios diversos donde refy outno están disponibles:
class Ref<T>
{
// Field rather than a property to support passing to functions
// accepting `ref T` or `out T`.
public T Value;
}
async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
var things = new[] { 0, 1, 2, };
var i = 0;
while (true)
{
// Fourth iteration will throw an exception, but we will still have
// communicated data back to the caller via successfulLoopsRef.
things[i] += i;
successfulLoopsRef.Value++;
i++;
}
}
async Task UsageExample()
{
var successCounterRef = new Ref<int>();
// Note that it does not make sense to access successCounterRef
// until OperationExampleAsync completes (either fails or succeeds)
// because there’s no synchronization. Here, I think of passing
// the variable as “temporarily giving ownership” of the referenced
// object to OperationExampleAsync. Deciding on conventions is up to
// you and belongs in documentation ^^.
try
{
await OperationExampleAsync(successCounterRef);
}
finally
{
Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
}
}
Amo el Trypatrón Es un patrón ordenado.
if (double.TryParse(name, out var result))
{
// handle success
}
else
{
// handle error
}
Pero, es un desafío con async. Eso no significa que no tengamos opciones reales. Estos son los tres enfoques principales que puede considerar para los asyncmétodos en una versión cuasi del Trypatrón.
Esto se parece más a un Trymétodo de sincronización que solo devuelve un en tuplelugar de un boolcon un outparámetro, que todos sabemos que no está permitido en C #.
var result = await DoAsync(name);
if (result.Success)
{
// handle success
}
else
{
// handle error
}
Con un método que devuelve truede falsey nunca tira una exception.
Recuerde, lanzar una excepción en un
Trymétodo rompe todo el propósito del patrón.
async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
return (true, await folder.GetFileAsync(fileName), null);
}
catch (Exception exception)
{
return (false, null, exception);
}
}
Podemos usar anonymousmétodos para establecer variables externas. Es una sintaxis inteligente, aunque un poco complicada. En pequeñas dosis, está bien.
var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
// handle success
}
else
{
// handle failure
}
El método obedece los conceptos básicos del Trypatrón, pero establece los outparámetros que se pasan en los métodos de devolución de llamada. Se hace así.
async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
try
{
var folder = ApplicationData.Current.LocalCacheFolder;
file?.Invoke(await folder.GetFileAsync(fileName));
return true;
}
catch (Exception exception)
{
error?.Invoke(exception);
return false;
}
}
Hay una pregunta en mi mente sobre el rendimiento aquí. Pero, el compilador de C # es tan increíblemente inteligente, que creo que está seguro de elegir esta opción, casi con seguridad.
¿Qué pasa si solo usa el TPLdiseño? No hay tuplas La idea aquí es que usemos excepciones para redirigir ContinueWitha dos caminos diferentes.
await DoAsync(name).ContinueWith(task =>
{
if (task.Exception != null)
{
// handle fail
}
if (task.Result is StorageFile sf)
{
// handle success
}
});
Con un método que arroja un exceptioncuando hay algún tipo de falla. Eso es diferente a devolver a boolean. Es una forma de comunicarse con el TPL.
async Task<StorageFile> DoAsync(string fileName)
{
var folder = ApplicationData.Current.LocalCacheFolder;
return await folder.GetFileAsync(fileName);
}
En el código anterior, si no se encuentra el archivo, se genera una excepción. Esto invocará la falla ContinueWithque manejará Task.Exceptionen su bloque lógico. Aseado, ¿eh?
Escucha, hay una razón por la que amamos el
Trypatrón. Es fundamentalmente tan ordenado y legible y, como resultado, mantenible. A medida que elija su enfoque, vigile la legibilidad. Recuerde el próximo desarrollador que en 6 meses y no tiene que responder preguntas aclaratorias. Su código puede ser la única documentación que tendrá un desarrollador.
La mejor de las suertes.
ContinueWithllamadas tiene el resultado esperado? Según tengo entendido, el segundo ContinueWithverificará el éxito de la primera continuación, no el éxito de la tarea original.
Tuve el mismo problema que me gusta usar el patrón de método Try, que básicamente parece ser incompatible con el paradigma async-waitit ...
Importante para mí es que puedo llamar al método Try dentro de una sola cláusula if y no tengo que predefinir las variables out antes, pero puedo hacerlo en línea como en el siguiente ejemplo:
if (TryReceive(out string msg))
{
// use msg
}
Entonces se me ocurrió la siguiente solución:
Defina una estructura auxiliar:
public struct AsyncOut<T, OUT>
{
private readonly T returnValue;
private readonly OUT result;
public AsyncOut(T returnValue, OUT result)
{
this.returnValue = returnValue;
this.result = result;
}
public T Out(out OUT result)
{
result = this.result;
return returnValue;
}
public T ReturnValue => returnValue;
public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) =>
new AsyncOut<T, OUT>(tuple.returnValue, tuple.result);
}
Defina el método de prueba asíncrono de esta manera:
public async Task<AsyncOut<bool, string>> TryReceiveAsync()
{
string message;
bool success;
// ...
return (success, message);
}
Llame al método Try asíncrono de esta manera:
if ((await TryReceiveAsync()).Out(out string msg))
{
// use msg
}
Para múltiples parámetros de salida, puede definir estructuras adicionales (por ejemplo, AsyncOut <T, OUT1, OUT2>) o puede devolver una tupla.
La limitación de los asyncmétodos que no aceptan outparámetros se aplica solo a los métodos asíncronos generados por el compilador, estos declarados con la asyncpalabra clave. No se aplica a los métodos asíncronos hechos a mano. En otras palabras, es posible crear Taskmétodos de retorno que acepten outparámetros. Por ejemplo, digamos que ya tenemos un ParseIntAsyncmétodo que arroja, y queremos crear uno TryParseIntAsyncque no arroje. Podríamos implementarlo así:
public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
var tcs = new TaskCompletionSource<int>();
result = tcs.Task;
return ParseIntAsync(s).ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.SetException(t.Exception.InnerException);
return false;
}
tcs.SetResult(t.Result);
return true;
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
}
Utilizando el TaskCompletionSourcey el ContinueWithmétodo es un poco incómodo, pero no hay otra opción, ya que no podemos usar el cómodo awaitpalabra clave dentro de este método.
Ejemplo de uso:
if (await TryParseIntAsync("-13", out var result))
{
Console.WriteLine($"Result: {await result}");
}
else
{
Console.WriteLine($"Parse failed");
}
Actualización: si la lógica asincrónica es demasiado compleja para expresarse sin ella await, entonces podría encapsularse dentro de un delegado anónimo asíncrono anidado. A TaskCompletionSourcetodavía sería necesario para el outparámetro. Es posible que el outparámetro se pueda completar antes de completar la tarea principal, como en el siguiente ejemplo:
public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
var tcs = new TaskCompletionSource<int>();
rawDataLength = tcs.Task;
return ((Func<Task<string>>)(async () =>
{
var response = await GetResponseAsync(url);
var rawData = await GetRawDataAsync(response);
tcs.SetResult(rawData.Length);
return await FilterDataAsync(rawData);
}))();
}
Este ejemplo supone la existencia de tres métodos asincrónicos GetResponseAsync, GetRawDataAsyncy FilterDataAsyncque se llaman en sucesión. El outparámetro se completa al completar el segundo método. El GetDataAsyncmétodo podría usarse así:
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");
Esperar lo dataanterior antes de esperar rawDataLengthes importante en este ejemplo simplificado, porque en caso de una excepción, el outparámetro nunca se completará.
Creo que usar ValueTuples como este puede funcionar. Sin embargo, primero debe agregar el paquete ValueTuple NuGet:
public async void Method1()
{
(int op, int result) tuple = await GetDataTaskAsync();
int op = tuple.op;
int result = tuple.result;
}
public async Task<(int op, int result)> GetDataTaskAsync()
{
int x = 5;
int y = 10;
return (op: x, result: y):
}
Aquí está el código de la respuesta de @ dcastro modificado para C # 7.0 con tuplas con nombre y deconstrucción de tuplas, que simplifica la notación:
public async void Method1()
{
// Version 1, named tuples:
// just to show how it works
/*
var tuple = await GetDataTaskAsync();
int op = tuple.paramOp;
int result = tuple.paramResult;
*/
// Version 2, tuple deconstruction:
// much shorter, most elegant
(int op, int result) = await GetDataTaskAsync();
}
public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
//...
return (1, 2);
}
Para obtener detalles sobre las nuevas tuplas con nombre, tuples literales y deconstrucciones de tuplas, consulte: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
Puede hacerlo utilizando TPL (biblioteca paralela de tareas) en lugar de usar directamente la palabra clave await.
private bool CheckInCategory(int? id, out Category category)
{
if (id == null || id == 0)
category = null;
else
category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;
return category != null;
}
if(!CheckInCategory(int? id, out var category)) return error