2014-07-17 4 views
12

S'il vous plaît, considérez le code exemple suivant:Comment gérer les effets secondaires produits par async/await lorsqu'il s'agit de types de valeurs mutables?

using System.Diagnostics; 
using System.Threading.Tasks; 

public struct AStruct 
{ 
    public int Value; 

    public async Task SetValueAsync() 
    { 
     Value = await Task.Run(() => 1); 
    } 
    public void SetValue() 
    { 
     Value = 1; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Test(new AStruct()); 
     TestAsync(new AStruct()).Wait(); 
    } 

    private static async Task TestAsync(AStruct x) 
    { 
     Debug.Assert(x.Value == 0); 
     await x.SetValueAsync(); 
     Debug.Assert(x.Value == 0); 
    } 

    private static void Test(AStruct x) 
    { 
     Debug.Assert(x.Value == 0); 
     x.SetValue(); 
     Debug.Assert(x.Value == 1); 
    } 
} 

Notez la différence entre Test et TestAsync. Ce code satisfait toutes les affirmations. Je suppose que regarder le code avec Reflector me dira pourquoi, mais c'est quelque chose que je ne m'attendais pas du tout.

Bien sûr, changer AStruct pour être une classe au lieu d'une structure échoue la deuxième assertion en TestAsync - comme je l'attendrais en premier lieu.

Ma question est la suivante - en plus de ne pas utiliser des structures mutables avec async/await, y at-il une manière élégante de les faire coexister pacifiquement?

+1

La mutation d'un type de valeur lorsqu'il s'agit d'un paramètre (sans 'ref') est totalement absurde. Async ne vient même pas dans cette image. – leppie

+1

@leppie Le problème est que SetValueAsync ne mute pas la variable qui l'appelle, et ce n'est pas le cas précisément parce que c'est une méthode 'async'. Le fait qu'il soit appelé sur une variable qui est un paramètre qui n'est pas passé par 'ref' n'est pas du tout pertinent. La mutation ne parvient même pas à faire muter la copie que 'TestAsync' fait de la structure créée dans' Main'. – Servy

+0

@Servy: Devinez la même chose ira pour un générateur (et probablement lors de l'utilisation d'une variable libre). – leppie

Répondre

10

Il est intrinsèquement impossible pour une méthode async d'un struct de muter "lui-même".

Ceci bien sûr prend tout son sens lorsque vous y réfléchissez. Au moment où les tâches await à l'intérieur de cette structure finissent réellement, étant donné que vous êtes retourné à l'appelant et leur a permis de continuer à faire toutes sortes de choses, vous n'avez aucun moyen de vous assurer que l'instance struct réelle la méthode n'existe même plus. Si SetValueAsync a été appelé sur une variable locale par une méthode qui n'a pas await ou Wait dessus ou quoi que ce soit de ce genre, la durée de vie de cette variable locale sera probablement terminée au moment où SetValueAsync atteint la suite de son appel à Run. Il ne peut pas muter la variable dont la durée de vie peut ou non être dans la portée. La seule option ici est pour async méthodes d'une structure pour se copier efficacement lorsque la méthode est appelée et avoir le code dans la référence de continuation une variable entièrement différente que la variable qui a appelé le async. Puisque la méthode fait une copie qui ne sera accessible nulle part ailleurs que dans le corps de cette méthode async, cela signifie que, dans tous les cas, une méthode async d'une structure ne peut jamais muter cette structure (et que la mutation soit visible par quelqu'un d'autre).

Vous pouvez avoir une méthode async d'un mutable struct, tant que cette méthode elle-même ne devrait pas muter le struct. Cette méthode devra renvoyer un Task<T> avec une nouvelle structure, ou quelque chose d'équivalent.

En tant tanget intéressante, il est dans les limites des possibilités techniques pour une méthode async d'un struct à muter avant la première await du mode si elle voulait vraiment. Le compilateur choisit de prendre la copie tout de suite, donc ce n'est pas réellement possible, mais le choix explicite a été fait de faire la copie au tout début de la méthode plutôt que seulement après la première await. C'est probablement pour le mieux, qu'il s'agisse d'une décision intentionnelle ou non, car cela serait déroutant autrement.

Questions connexes