¿Pueden los constructores ser asincrónicos?

Tengo un proyecto de Silverlight en el que trato de completar algunos datos en un constructor:

public class ViewModel { public ObservableCollection Data { get; set; } async public ViewModel() { Data = await GetDataTask(); } public Task<ObservableCollection> GetDataTask() { Task<ObservableCollection> task; //Create a task which represents getting the data return task; } } 

Lamentablemente, recibo un error:

El modificador async no es válido para este artículo

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

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

funciona bien. Del mismo modo, si uso la vieja forma de adentro hacia afuera

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

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

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 ” void y olvidar” void o Task .

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

Si el constructor asíncrono se comportó de la misma manera que un método async void , ese tipo de rompe lo que el constructor debe ser. Después de que el constructor regrese, deberías obtener un objeto completamente inicializado. No es un objeto que se inicialice correctamente en algún punto indefinido en el futuro. Es decir, si tiene suerte y la inicialización asincrónica no falla.

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

Si realmente quieres la semántica de “disparar y olvidar” de los métodos de async void (que deben evitarse, si es posible), puedes encapsular fácilmente todo el código en un método de async void y llamarlo desde tu constructor, como mencionaste en la pregunta .

Como no es posible crear un constructor asíncrono, utilizo 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 Data { get; set; } //static async method that behave like a constructor async public static Task BuildViewModelAsync() { ObservableCollection tmpData = await GetDataTask(); return new ViewModel(tmpData); } // private constructor called by the async method private ViewModel(ObservableCollection Data) { this.Data=Data; } } 

Su problema es comparable a la creación de un objeto de archivo y la apertura del archivo. De hecho, hay muchas clases en las que debe realizar dos pasos antes de poder usar realmente el objeto: create + Initialize (a menudo llamado algo similar a Open).

La ventaja de esto es que el constructor puede ser liviano. Si lo desea, puede cambiar algunas propiedades antes de inicializar realmente el objeto. Cuando se establecen todas las propiedades, se llama a la función Initialize / Open para preparar el objeto que se utilizará. Esta función de Initialize puede ser asincrónica.

La desventaja es que debe confiar en el usuario de su clase que llamará Initialize() antes de usar cualquier otra función de su clase. De hecho, si desea hacer que su clase tenga una prueba completa (¿a prueba de tontos?), Debe verificar cada función a la que se ha llamado Initialize() .

El patrón para hacerlo más fácil es declarar el constructor privado y crear una función pública estática que construirá el objeto y llamará a Initialize() antes de devolver el objeto construido. De esta forma sabrá que todos los que tienen acceso al objeto han usado la función de Initialize .

El ejemplo muestra una clase que imita tu constructor asíncrono deseado

 public MyClass { public static async Task CreateAsync(...) { MyClass x = new MyClass(); await x.InitializeAsync(...) return x; } // make sure no one but the Create function can call the constructor: private MyClass(){} private async Task InitializeAsync(...) { // do the async things you wanted to do in your async constructor } public async Task OtherFunctionAsync(int a, int b) { return await OtherFunctionAsync(a, b); } 

El uso será el siguiente:

 public async Task SomethingAsync() { // Create and initialize a MyClass object MyClass myObject = await MyClass.CreateAsync(...); // use the created object: return await myObject.OtherFunctionAsync(4, 7); } 

En este caso particular, se requiere un modelo de vista para iniciar la tarea y notificar a la vista una vez completada. Una “propiedad asíncrona”, no un “constructor asíncrono”, está en orden.

Acabo de lanzar AsyncMVVM , que resuelve exactamente este problema (entre otros). En caso de usarlo, su ViewModel se convertiría en:

 public class ViewModel : AsyncBindableBase { public ObservableCollection Data { get { return Property.Get(GetDataAsync); } } private Task> GetDataAsync() { //Get the data asynchronously } } 

Por extraño que parezca, Silverlight es compatible. 🙂

si hace el constructor asíncrono, después de crear un objeto, puede tener 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.

Me preguntaba por qué no podemos llamar en await directamente desde un constructor.

Creo que la respuesta breve es simple: porque el equipo .Net no ha progtwigdo esta característica.

Creo que con la syntax correcta esto podría implementarse y no debería ser demasiado confuso o propenso a errores. Creo que la publicación de Stephen Cleary en el blog y varias otras respuestas aquí han señalado implícitamente que no hay una razón fundamental en su contra, y más que eso: resolvieron esa falta con soluciones provisionales. 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.

No estoy familiarizado con la palabra clave async (¿es esto específico para Silverlight o una nueva característica en la versión beta de Visual Studio?), Pero creo que puedo darle una idea de por qué no puede hacer esto.

Si lo hago:

 var o = new MyObject(); MessageBox(o.SomeProperty.ToString()); 

o puede que no se realice la inicialización antes de que se ejecute la siguiente línea de código. No se puede asignar una instancia de su objeto hasta que se complete su constructor, y hacer que el constructor asincrónico no cambie eso, ¿cuál sería el punto? Sin embargo, podría llamar a un método asíncrono desde su constructor y luego su constructor podría completar y obtendría la creación de instancias mientras el método asíncrono sigue haciendo lo que sea necesario para configurar su objeto.

puedes usar Acción dentro de Constructor

  public class ViewModel { public ObservableCollection Data { get; set; } public ViewModel() { new Action(async () => { Data = await GetDataTask(); }).Invoke(); } public Task> GetDataTask() { Task> task; //Create a task which represents getting the data return task; } } 

Utilizo 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 */ } } 

Yo usaría algo como esto.

  public class MyViewModel { public MyDataTable Data { get; set; } public MyViewModel() { loadData(() => GetData()); } private async void loadData(Func 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 obtener para los constructores.