web-dev-qa-db-fra.com

c # démarrer la méthode asynchrone dans le constructeur d’objet - mauvaise pratique?

j'ai du code dans un constructeur d'objet similaire à

delegate DataSet MyInvoker;

public MyObject(Param1 p1)
{
    // property sets here
    // ...

    BeginMyAsyncMethod();
}

public void BeginMyAsyncMethod() 
{
    // set some properties
    // ...

    MyInvoker inv = new MyInvoker(SomeBeginMethod);
    inv.BeginInvoke(new AsyncCallback(SomeEndMethod), null);
}

Mes questions sont:

  1. Est-ce généralement considéré comme une mauvaise pratique?
  2. Serait-il préférable (ou judicieux) de définir dans ma classe une méthode de démarrage que l'utilisateur appellerait pour effectuer l'action asynchrone?

Cette réponse me donne l’impression que le laisser à l’utilisateur est une mauvaise pratique, bien que je parle spécifiquement de démarrer des méthodes asynchrones dans le constructeur, et non de la construction correcte d’un objet.

25
ghostJago

Ceci est facilement accompli avec une approche légèrement différente. En réalité, cela arrive tout le temps, n'est-ce pas? Voici une solution simple pour vous donner une option sans faire quelque chose de stupide:

public class MyResource
{
    // do this instead of a constructor
    public async Task<MyResource> StartAsync()
    {
        await Task.Delay(1);
        return this;
    }
}

public class MyControl
{
    public MyResource Resource { get; set; }
    async void Button_Click(object s, EventArgs e)
    {
        // call start as if it is a constructor
        this.Resource = await new MyResource().StartAsync();
    }
}
22
Jerry Nixon - MSFT

Généralement, il n'est pas conseillé de faire dans votre constructeur quelque chose qui ne soit pas directement lié à la création de l'instance d'objet. Par exemple, si je ne connaissais pas l'objectif de votre classe MyObject, je ne saurais peut-être pas qu'elle génère un processus asynchrone lorsque je crée une nouvelle instance. C'est généralement une mauvaise pratique.

On dirait presque que vous dites; hé, créez cet objet, et il ne sera utilisable que lorsque ce processus asynchrone sera terminé; c'est contre-intuitif.

Si vous voulez faire quelque chose comme ceci, je penserais que vous seriez certainement mieux servi en allant dans un patron d'usine; vous appelez une fabrique comme ceci et il va créer l'objet et appeler la méthode pour vous:

 var instance = MyObjectBuilder.CreateInstance();

 // Internally, this does
 //    var x = new MyObject();
 //    x.Initilizatize();
 //    return x;

Si votre objet ne sera utilisable qu'une fois son initialisation terminée, vous devriez probablement exposer une propriété pour vérifier si elle est prête, comme ceci:

 instance.WaitForReady(); // blocking version
 if(instance.IsReady) // non blocking check
12
Tejs

Oui parce que vous introduisez potentiellement des comportements inattendus. Les objets doivent être construits et exempts d'exceptions et de comportements inattendus. Si les actions asynchrones se produisent toujours après la construction de l'objet et que l'utilisateur essaie de l'utiliser ...

De plus, que se passe-t-il si la méthode asynchrone est toujours en cours d'exécution et que l'utilisateur décide de détruire l'objet?

Une bonne lecture: http://www.Amazon.com/Framework-Design-Guidelines-Conventions-Libraries/dp/0321545613/ref=sr_1_1?s=books&ie=UTF8&qid=1314813265&sr=1-1

Si votre méthode asynchrone doit être asynchrone parce que le processus est long, elle doit être déplacée vers sa propre méthode appelée par l'utilisateur, et non pas lorsque l'objet est instatié.

Vous avez également le problème potentiel de conserver une référence à l'objet si l'utilisateur décide de le détruire avant la méthode asynchrone, ce qui signifie que vous risquez une fuite de mémoire.

Si cela doit être fait pour initialiser l'objet, il convient de le créer à l'aide d'une fabrique ou d'un constructeur.

8
Dustin Davis

C'est certainement une mauvaise idée d'initier quoi que ce soit dans un constructeur susceptible de rappeler l'instance sur un autre thread avant que l'instance ne soit entièrement construite. Même s'il s'agit de la dernière ligne de your constructeur, le rappel peut être appelé avant la fin de l'exécution d'un constructeur de sous-classe puisque le code de constructeur de sous-classe est exécuté après le code de constructeur de classe de base. Même si votre classe est scellée aujourd'hui, elle risque de l'être à l'avenir, et rechercher ce type de problème si une sous-classe était ajoutée à l'avenir coûterait très probablement très cher.

En gros, si vous ne voulez pas finir par chasser les heisenbugs, ne faites jamais ce genre de chose avec un constructeur. Au lieu de cela, ajoutez une méthode "start" comme celle que vous avez mentionnée dans votre question n ° 2.

5
Nicole Calinoiu

Je suis d'accord, mauvaise idée. Ajouter une méthode d'initialisation. Vous pouvez également ajouter une méthode statique qui renvoie le nouvel objet et exécute la méthode Initialize. De cette façon, vous pouvez également garder le constructeur en privé.

public class XXX
{
    private XXX()
    {
    }

    private void DoMyWeirdThingsAsynchronously()
    {
        ....
    }

    public static XXX PerformSomethingStrange()
    {
        XXX result = new XXX();
        result.DoMyWeirdThingsAsynchronously();
        return result;
    }
}
5
Salvatore Previti

Personnellement, j'éviterais de mettre un tel code dans un constructeur. Il serait préférable de fournir une méthode spécifique pour démarrer l'opération asynchrone. Lancer des exceptions à partir d'un constructeur (autre que des exceptions de validation) est une mauvaise idée.

0
Darin Dimitrov