Je veux écrire une méthode asynchrone avec un paramètre out
, comme ceci :
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
Comment puis-je faire cela dans GetDataTaskAsync
?
Je veux écrire une méthode asynchrone avec un paramètre out
, comme ceci :
public async void Method1()
{
int op;
int result = await GetDataTaskAsync(out op);
}
Comment puis-je faire cela dans GetDataTaskAsync
?
Une belle caractéristique des paramètres out
est qu'ils peuvent être utilisés pour renvoyer des données même lorsqu'une fonction lance une exception. Je pense que l'équivalent le plus proche pour faire cela avec une méthode async
serait d'utiliser un nouvel objet pour stocker les données auxquelles la méthode async
et l'appelant peuvent se référer. Une autre façon serait de passer un délégué comme suggéré dans une autre réponse.
Remarquez que aucune de ces techniques n'aura le même type d'application de la part du compilateur que out
. Autrement dit, le compilateur ne vous obligera pas à définir la valeur sur l'objet partagé ou à appeler un délégué passé en paramètre.
Voici un exemple d'implémentation utilisant un objet partagé pour imiter ref
et out
pour une utilisation avec des méthodes async
et d'autres scénarios divers où ref
et out
ne sont pas disponibles:
class Ref
{
// Champs plutôt qu'une propriété pour supporter le passage aux fonctions
// acceptant `ref T` ou `out T`.
public T Value;
}
async Task OperationExampleAsync(Ref successfulLoopsRef)
{
var things = new[] { 0, 1, 2, };
var i = 0;
while (true)
{
// La quatrième itération va déclencher une exception, mais nous aurons quand même
// communiqué des données au rappelant via successfulLoopsRef.
things[i] += i;
successfulLoopsRef.Value++;
i++;
}
}
async Task UsageExample()
{
var successCounterRef = new Ref();
// Remarquez qu'il n'a pas de sens d'accéder à successCounterRef
// avant que OperationExampleAsync ne se termine (échec ou succès)
// car il n'y a pas de synchronisation. Ici, je pense à passer
// la variable comme "donner temporairement la propriété" de l'objet référencé à OperationExampleAsync. Décider des conventions relève de
// vous et cela appartient à la documentation ^^.
try
{
await OperationExampleAsync(successCounterRef);
}
finally
{
Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
}
}
J'adore le motif Essayer
. C'est un motif soigné.
if (double.TryParse(name, out var result))
{
// gérer le succès
}
else
{
// gérer l'erreur
}
Mais, c'est difficile avec async
. Cela ne signifie pas que nous n'avons pas de réelles options. Voici les trois approches principales que vous pouvez envisager pour les méthodes async
dans une version quasi du motif Essayer
.
Cela ressemble le plus à une méthode sync Essayer
retournant seulement un tuple
au lieu d'un booléen
avec un paramètre out
, ce qui n'est pas autorisé en C#.
var result = await DoAsync(name);
if (result.Success)
{
// gérer le succès
}
else
{
// gérer l'erreur
}
Avec une méthode qui retourne true
ou false
et ne lance jamais d'exception.
N'oubliez pas, lancer une exception dans une méthode
Essayer
casse tout l'objectif du motif.
async Task<(bool Succès, StorageFile Fichier, Exception exception)> DoAsync(string fileName)
{
try
{
var dossier = ApplicationData.Current.LocalCacheFolder;
return (true, await dossier.GetFileAsync(fileName), null);
}
catch (Exception exception)
{
return (false, null, exception);
}
}
Nous pouvons utiliser des méthodes anonymes
pour définir des variables externes. C'est une syntaxe intelligente, bien que légèrement compliquée. En petites quantités, c'est bien.
var fichier = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => fichier = x, x => exception = x))
{
// gérer le succès
}
else
{
// gérer l'échec
}
La méthode respecte les bases du motif Essayer
mais défini les paramètres out
des méthodes de rappel passées en paramètre. C'est fait de cette manière.
async Task DoAsync(string fileName, Action fichier, Action erreur)
{
try
{
var dossier = ApplicationData.Current.LocalCacheFolder;
fichier?.Invoke(await dossier.GetFileAsync(fileName));
return true;
}
catch (Exception exception)
{
erreur?.Invoke(exception);
return false;
}
}
J'ai une question concernant les performances ici. Mais, le compilateur C# est tellement intelligent, que je pense que vous êtes sûr de choisir cette option, presque à coup sûr.
Et si vous utilisez simplement le TPL
tel qu'il est conçu ? Pas de tuples. L'idée ici est que nous utilisons des exceptions pour rediriger ContinueWith
vers deux chemins différents.
await DoAsync(name).ContinueWith(tâche =>
{
if (tâche.Exception != null)
{
// gérer l'échec
}
if (tâche.Result is StorageFile sf)
{
// gérer le succès
}
});
Avec une méthode qui lance une exception
en cas d'échec. C'est différent du retour d'un booléen
. C'est une façon de communiquer avec le TPL
.
async Task DoAsync(string fileName)
{
var dossier = ApplicationData.Current.LocalCacheFolder;
return await dossier.GetFileAsync(fileName);
}
Dans le code ci-dessus, si le fichier n'est pas trouvé, une exception est lancée. Cela invoquera l'échec de ContinueWith
qui gérera Task.Exception
dans son bloc logique. Pas mal, n'est-ce pas ?
Écoutez, il y a une raison pour laquelle nous aimons le motif
Essayer
. C'est tellement propre, lisible et, par conséquent, maintenable. En choisissant votre approche, surveillez la lisibilité. Rappelez-vous du prochain développeur qui sera là dans 6 mois et n'aura personne pour répondre à ses questions. Votre code peut être la seule documentation qu'un développeur aura jamais.
Bonne chance.
Voici le code de la réponse de @dcastro modifié pour C# 7.0 avec tuples nommés et déconstruction de tuples, ce qui simplifie la notation :
public async void Method1()
{
// Version 1, tuples nommés :
// juste pour montrer comment ça fonctionne
/*
var tuple = await GetDataTaskAsync();
int op = tuple.paramOp;
int result = tuple.paramResult;
*/
// Version 2, déconstruction de tuple :
// beaucoup plus court, plus élégant
(int op, int result) = await GetDataTaskAsync();
}
public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
//...
return (1, 2);
}
Pour plus de détails sur les nouveaux tuples nommés, littéraux de tuple et déconstructions de tuple, consultez : https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/
La limitation des méthodes async
de n'accepter pas les paramètres out
s'applique uniquement aux méthodes async générées par le compilateur, celles déclarées avec le mot-clé async
. Cela ne s'applique pas aux méthodes async faites à la main. En d'autres termes, il est possible de créer des méthodes renvoyant des Task
acceptant des paramètres out
. Par exemple, disons que nous avons déjà une méthode ParseIntAsync
qui lance une exception, et nous voulons créer une méthode TryParseIntAsync
qui ne lance pas d'exception. Nous pourrions l'implémenter de cette manière :
public static Task TryParseIntAsync(string s, out Task result)
{
var tcs = new TaskCompletionSource();
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);
}
L'utilisation de la TaskCompletionSource
et la méthode ContinueWith
est un peu maladroite, mais il n'y a pas d'autre option car nous ne pouvons pas utiliser le mot-clé await
pratique à l'intérieur de cette méthode.
Exemple d'utilisation :
if (await TryParseIntAsync("-13", out var result))
{
Console.WriteLine($"Résultat: {await result}");
}
else
{
Console.WriteLine($"Échec de l'analyse");
}
Mise à jour : Si la logique async est trop complexe pour être exprimée sans await
, elle peut être encapsulée à l'intérieur d'une déléguée anonyme asynchrone imbriquée. Un TaskCompletionSource
serait tout de même nécessaire pour le paramètre out
. Il est possible que le paramètre out
soit complété avant l'achèvement de la tâche principale, comme dans l'exemple ci-dessous :
public static Task GetDataAsync(string url, out Task rawDataLength)
{
var tcs = new TaskCompletionSource();
rawDataLength = tcs.Task;
return ((Func>)(async () =>
{
var response = await GetResponseAsync(url);
var rawData = await GetRawDataAsync(response);
tcs.SetResult(rawData.Length);
return await FilterDataAsync(rawData);
}))();
}
Cet exemple suppose l'existence de trois méthodes asynchrones GetResponseAsync
, GetRawDataAsync
et FilterDataAsync
qui sont appelées successivement. Le paramètre out
est complété à la fin de la deuxième méthode. La méthode GetDataAsync
pourrait être utilisée comme ceci :
var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Données : {data}");
Console.WriteLine($"Longueur des données brutes : {await rawDataLength}");
Attendre les data
avant d'attendre les rawDataLength
est important dans cet exemple simplifié, car en cas d'exception le paramètre out
ne sera jamais complété.
Je pense que l'utilisation de ValueTuples comme ceci peut fonctionner. Vous devez d'abord ajouter le package NuGet ValueTuple :
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):
}
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.