c# - remarks - ¿Pueden los constructores ser asíncronos?
see cref c# (11)
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.
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 .
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. :)
No estoy familiarizado con la palabra clave asíncrona (¿es esto específico de Silverlight o una nueva función 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 no se puede hacer la inicialización antes de que se ejecute la siguiente línea de código. No se puede asignar una instanciación de su objeto hasta que se complete su constructor, y hacer que el constructor sea asíncrono no cambiaría eso, ¿cuál sería el punto? Sin embargo, puede llamar a un método asíncrono desde su constructor y luego su constructor podría completarse y obtendría su instanciación mientras el método asíncrono todavía está haciendo lo que sea necesario para configurar su objeto.
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 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.
Su problema es comparable a la creación de un objeto de archivo y abrir el archivo. De hecho, hay muchas clases en las que tiene que realizar dos pasos antes de poder usar el objeto: crear + Inicializar (a menudo llamado algo similar a Abrir).
La ventaja de esto es que el constructor puede ser ligero. 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 asíncrona.
La desventaja es que tiene que confiar en el usuario de su clase que llamará Initialize()
antes de usar cualquier otra función de su clase. De hecho, si desea que su clase sea una prueba completa (a prueba de tontos?), Debe verificar en cada función que se haya llamado a Initialize()
.
El patrón para hacer esto más fácil es declarar privado al constructor y hacer una función estática pública que construirá el objeto y llamará a Initialize()
antes de devolver el objeto construido. De esta manera, sabrá que todos los que tienen acceso al objeto han utilizado la función Initialize
.
El ejemplo muestra una clase que imita su constructor asíncrono deseado
public MyClass
{
public static async Task<MyClass> 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<int> OtherFunctionAsync(int a, int b)
{
return await OtherFunctionAsync(a, b);
}
El uso será el siguiente:
public async Task<int> SomethingAsync()
{
// Create and initialize a MyClass object
MyClass myObject = await MyClass.CreateAsync(...);
// use the created object:
return await myObject.OtherFunctionAsync(4, 7);
}
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.
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
*/
}
}
llamar a async en el constructor puede causar un interbloqueo, consulte http://social.msdn.microsoft.com/Forums/en/winappswithcsharp/thread/0d24419e-36ad-4157-abb5-3d9e6c5dacf1
http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx