c# remarks - ¿Pueden los constructores ser asíncronos?





summary example (10)


Yo uso este truco fácil.

public sealed partial class NamePage
{
  private readonly Task _initializingTask;

  public NamePage()
  {
    _initializingTask = Init();
  }

  private async Task Init()
  {
    /*
    Initialization that you need with await/async stuff allowed
    */
  }
}

Tengo un proyecto de Silverlight en el que intento rellenar algunos datos en un constructor:

public class ViewModel
{
    public ObservableCollection<TData> Data { get; set; }

    async public ViewModel()
    {
        Data = await GetDataTask();
    }

    public Task<ObservableCollection<TData>> GetDataTask()
    {
        Task<ObservableCollection<TData>> task;

        //Create a task which represents getting the data
        return task;
    }
}

Desafortunadamente, estoy recibiendo un error:

El modificador async no es válido para este elemento.

Por supuesto, si envuelvo un método estándar y lo llamo desde el constructor:

public async void Foo()
{
    Data = await GetDataTask();
}

funciona bien. Igualmente, si utilizo el antiguo camino de adentro hacia afuera.

GetData().ContinueWith(t => Data = t.Result);

Eso también funciona. Me preguntaba por qué no podemos llamar a la await desde dentro de un constructor directamente. Probablemente hay muchos casos de borde (incluso obvios) y razones en contra, simplemente no puedo pensar en ninguno. También busqué una explicación, pero parece que no puedo encontrar ninguna.




Me preguntaba por qué no podemos llamar a la await desde dentro de un constructor directamente.

Creo que la respuesta corta es simple: porque el equipo de .Net no ha programado esta función.

Creo que con la sintaxis correcta esto podría implementarse y no debería ser demasiado confuso o propenso a errores. Creo que la blog el blog Stephen Cleary y varias otras respuestas aquí han señalado implícitamente que no hay una razón fundamental en contra, y más que eso: resolvió esa falta con soluciones alternativas. La existencia de estas soluciones relativamente simples es probablemente una de las razones por las que esta característica aún no se ha implementado.




En este caso particular, se requiere un viewModel para iniciar la tarea y notificar a la vista una vez finalizada. Una "propiedad asíncrona", no un "constructor asíncrono", está en orden.

Acabo de lanzar AsyncMVVM , que resuelve exactamente este problema (entre otros). Si lo usas, tu ViewModel se convertiría en:

public class ViewModel : AsyncBindableBase
{
    public ObservableCollection<TData> Data
    {
        get { return Property.Get(GetDataAsync); }
    }

    private Task<ObservableCollection<TData>> GetDataAsync()
    {
        //Get the data asynchronously
    }
}

Curiosamente, Silverlight es compatible. :)




Yo usaría algo como esto.

 public class MyViewModel
    {
            public MyDataTable Data { get; set; }
            public MyViewModel()
               {
                   loadData(() => GetData());
               }
               private async void loadData(Func<DataTable> load)
               {
                  try
                  {
                      MyDataTable = await Task.Run(load);
                  }
                  catch (Exception ex)
                  {
                       //log
                  }
               }
               private DataTable GetData()
               {
                    DataTable data;
                    // get data and return
                    return data;
               }
    }

Esto es lo más cercano que puedo conseguir para los constructores.







Si hace que el constructor sea asíncrono, después de crear un objeto, puede caer en problemas como valores nulos en lugar de objetos de instancia. Por ejemplo;

MyClass instance = new MyClass();
instance.Foo(); // null exception here

Es por eso que no permiten esto, supongo.




Como no es posible hacer un constructor asíncrono, uso un método asíncrono estático que devuelve una instancia de clase creada por un constructor privado. Esto no es elegante pero funciona bien.

   public class ViewModel       
   {       
    public ObservableCollection<TData> Data { get; set; }       

    //static async method that behave like a constructor       
    async public static Task<ViewModel> BuildViewModelAsync()  
    {       
     ObservableCollection<TData> tmpData = await GetDataTask();  
     return new ViewModel(tmpData);
    }       

    // private constructor called by the async method
    private ViewModel(ObservableCollection<TData> Data)
    {
     this.Data=Data;   
    }
   }  



El constructor actúa de manera muy similar a un método que devuelve el tipo construido. Y el método async no puede devolver cualquier tipo, tiene que ser "deshabilitar y olvidar" o Task .

Si el constructor de tipo T realmente devolviera la Task<T> , creo que sería muy confuso.

Si el constructor asíncrono se comportó de la misma manera que un método de async void , ese tipo de ruptura se entenderá como debe ser el constructor. Después de que el constructor regrese, debe obtener un objeto completamente inicializado. No es un objeto que se inicialice correctamente en algún momento indefinido en el futuro. Es decir, si tienes suerte y la inicialización asíncrona no falla.

Todo esto es solo una conjetura. Pero me parece que tener la posibilidad de un constructor asíncrono trae más problemas de los que vale la pena.

Si realmente desea la semántica de "disparar y olvidar" de los métodos async void (que deben evitarse, si es posible), puede encapsular fácilmente todo el código en un método async void y llamar a su constructor, como mencionó en la pregunta .




Puedes usar la acción dentro del constructor.

 public class ViewModel
    {
        public ObservableCollection<TData> Data { get; set; }
       public ViewModel()
        {              
            new Action(async () =>
            {
                  Data = await GetDataTask();
            }).Invoke();
        }

        public Task<ObservableCollection<TData>> GetDataTask()
        {
            Task<ObservableCollection<TData>> task;
            //Create a task which represents getting the data
            return task;
        }
    }



Si quieres ser malo, puedes usar el operador "nuevo" en el lugar:

class Foo() {
    Foo() { /* default constructor deliciousness */ }
    Foo(Bar myParam) {
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Parece funcionar para mi.

editar

Como señala @ElvedinHamzagic, si Foo contenía un objeto que asignó memoria, ese objeto podría no ser liberado. Esto complica aún más las cosas.

Un ejemplo más general:

class Foo() {
private:
  std::vector<int> Stuff;
public:
    Foo()
      : Stuff(42)
    {
      /* default constructor deliciousness */
    }

    Foo(Bar myParam)
    {
      this->~Foo();
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Parece un poco menos elegante, seguro. La solución de @ JohnIdol es mucho mejor.





c# constructor async-await