构造函数可以异步吗?

Mar*_*eal 262 c# constructor async-await

我有一个Silverlight项目,我试图在构造函数中填充一些数据:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我收到一个错误:

修饰符async对此项目无效

当然,如果我在标准方法中包装并从构造函数中调用它:

public async void Foo()
{
    Data = await GetDataTask();
}
Run Code Online (Sandbox Code Playgroud)

它工作正常.同样,如果我使用旧的由内而外的方式

GetData().ContinueWith(t => Data = t.Result);
Run Code Online (Sandbox Code Playgroud)

这也有效.我只是想知道为什么我们不能await直接在构造函数内调用.可能有很多(甚至是明显的)边缘情况和反对它的理由,我只是想不出来.我也在寻找解释,但似乎找不到任何解释.

Pie*_*off 214

由于无法创建异步构造函数,因此我使用静态异步方法返回由私有构造函数创建的类实例.这不是很优雅,但它可以正常工作.

   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;   
    }
   }  
Run Code Online (Sandbox Code Playgroud)

  • 在我看来,这个答案应该有更多的选票.它给出了一个答案,它封装并隐藏了在构造项之后调用Initialize()方法的需要,从而防止了构造对象和忘记对其初始化方法进行校准的潜在错误. (7认同)
  • 此方法使用[工厂模式](http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html).请参阅另一个写得很好的类似答案[这里](http://stackoverflow.com/a/34311951/1497596). (6认同)
  • Ag,如果您可以控制构造函数,那么这将是一个很好的解决方案,但如果您的类实现了抽象基类,例如 public class LoginModelValidator : AbstractValidator&lt;Domain.Models.LoginModel&gt; 您就会遇到问题 (3认同)
  • 你并不总是能控制调用者,所以工厂不是*总是*通用的解决方案(重申 [Damian 所说的](http://stackoverflow.com/questions/8145479/can-constructors-be-async #comment55926301_12520574)以更一般的方式) (2认同)
  • 从“用户”的角度来看,这是一个很好的解决方案,但它在 ie Web 应用程序中很常见,并且您需要大量样板。如果他们能在类似于异步构造函数的东西中“语法糖”这种行为,那就太好了。 (2认同)

svi*_*ick 194

构造函数与返回构造类型的方法非常相似.并且async方法不能返回任何类型,它必须是"火与忘记" void,或者Task.

如果类型的构造函数T实际返回Task<T>,那将是非常混乱,我想.

如果异步构造函数的行为方式与async void方法相同,则会破坏构造函数的含义.构造函数返回后,您应该获得一个完全初始化的对象.不是将来在某个未定义的点上实际正确初始化的对象.也就是说,如果你很幸运,异步初始化不会失败.

这一切只是猜测.但在我看来,有一个异步构造函数的可能性会带来更多的麻烦而不是它的价值.

如果你真的想要方法的"即发即忘"语义async void(如果可能的话应该避免),你可以轻松地将所有代码封装在一个async void方法中,并从你的构造函数中调用它,正如你在问题中提到的那样.

  • "如果类型T的构造函数实际上返回了Task <T>,那我认为这将是非常令人困惑的." 我不同意.像async Dispose一样,它会非常自然. (14认同)
  • 我认为这最接近它.`await`可以经常取代`.ContinueWith`,我很容易忘记它不是那么简单.我甚至不确定我在想什么,但我想我认为`await`应该"返回"一个构造的`T`(你指出它不是异步方法可以返回的)因为那是构造函数"返回"但是当await继续时,构造函数不会返回任何内容,因为它是一个构造函数,如`void`.我甚至没有意义,但你的回答是最有帮助的.谢谢. (4认同)
  • “async void”不要这样做。对象的构造尚未完成。它可以引发不会被处理的异常等。 (3认同)

Har*_*lse 48

您的问题与创建文件对象和打开文件相当.事实上,在实际使用对象之前,有许多类需要执行两个步骤:create + Initialize(通常称为Open类似的东西).

这样做的好处是构造函数可以是轻量级的.如果需要,您可以在实际初始化对象之前更改某些属性.设置所有属性后,将调用Initialize/ Open函数以准备要使用的对象.此Initialize功能可以是异步的.

缺点是你必须相信Initialize()他所使用的课程的用户,在他使用你班级的任何其他功能之前.事实上,如果你想让你的课程充分证明(傻瓜证明?),你必须检查Initialize()所调用的每个函数.

使这更容易的模式是将构造函数声明为private并创建一个公共静态函数,该函数将Initialize()在返回构造对象之前构造对象并调用.这样您就会知道有权访问该对象的每个人都使用过该Initialize功能.

该示例显示了一个模仿您所需的异步构造函数的类

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 ... // return something useful
    }
Run Code Online (Sandbox Code Playgroud)

用法如下:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 想法不是使用构造函数,而是构造对象的静态函数ans async初始化它.所以不要在构造函数中进行初始化,但是在一个单独的私有Initialize函数中,这个Initialize函数可以返回一个等待的Task,因此静态Create函数可以返回一个等待的任务 (3认同)
  • 从现在开始,我将其称为“异步构造器模式”。-IMO,这应该是公认的答案,因为它很好,很简单,也很重要-很好! (2认同)