构造函数可以异步吗?

时间:2011-11-16 01:19:49

标签: c# constructor async-await

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

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;
    }
}

不幸的是,我收到了错误:

  

修饰符async对此项无效

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

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

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

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

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

13 个答案:

答案 0 :(得分:185)

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

   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;   
    }
   }  

答案 1 :(得分:173)

构造函数与返回构造类型的方法非常相似。并且async方法不能返回任何类型,它必须是“一劳永逸”voidTask

如果T类型的构造函数实际返回Task<T>,那我认为这会非常混乱。

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

这一切只是猜测。但在我看来,拥有异步构造函数的可能性会带来更多的麻烦。

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

答案 2 :(得分:38)

您的问题与创建文件对象和打开文件相当。事实上,在实际使用对象之前,有很多类需要执行两个步骤: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 OtherFunctionAsync(a, b);
    }

用法如下:

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);
}

答案 3 :(得分:4)

在这种特殊情况下,需要viewModel来启动任务并在视图完成时通知视图。 “异步属性”,而不是“异步构造函数”,按顺序排列。

我刚刚发布了AsyncMVVM,它解决了这个问题(等等)。如果您使用它,您的ViewModel将变为:

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

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

奇怪的是,支持Silverlight。 :)

答案 4 :(得分:2)

如果你使构造函数异步,在创建一个对象之后,你可能会遇到像null值而不是实例对象这样的问题。例如;

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

这就是为什么他们不允许这样做的原因。

答案 5 :(得分:2)

  

我只是想知道为什么我们不能直接在构造函数中调用await

我认为简短的回答很简单:因为.Net团队没有编写此功能。

我相信使用正确的语法可以实现,不应该太混乱或容易出错。我认为Stephen Cleary的blog post以及其他几个答案已经含蓄地指出,没有根本原因可以反对它,而不仅仅是 - 用解决方法解决了这个问题。这些相对简单的解决方法的存在可能是此功能尚未实现的原因之一。

答案 6 :(得分:1)

答案 7 :(得分:0)

某些答案涉及创建新的public方法。无需执行此操作,请使用Lazy<T>类:

public class ViewModel
{
    private Lazy<ObservableCollection<TData>> Data;

    async public ViewModel()
    {
        Data = new Lazy<ObservableCollection<TData>>(GetDataTask);
    }

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

        //Create a task which represents getting the data
        return task.GetAwaiter().GetResult();
    }
}

要使用Data,请使用Data.Value

答案 8 :(得分:-2)

我不熟悉async关键字(这是特定于Silverlight还是Visual Studio测试版中的新功能?),但我想我可以告诉你为什么不能这样做。

如果我这样做:

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

o可能无法在下一行代码运行之前进行初始化。在构造函数完成之前,无法分配对象的实例化,并且使构造函数异步不会改变那么那么重点是什么?但是,您可以从构造函数中调用异步方法,然后您的构造函数可以完成,并且您将获得实例化,而异步方法仍在执行设置对象所需的任何操作。

答案 9 :(得分:-2)

您可以在构造函数

中使用Action
 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;
        }
    }

答案 10 :(得分:-2)

我使用这个简单的技巧。

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

答案 11 :(得分:-2)

我会用这样的东西。

 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;
               }
    }

这与构造函数的结果非常接近。

答案 12 :(得分:-2)

这是创建样式的好地方。这是一个懒惰的初始化。构造函数不能是异步的,它必须是原子的并且具有具体的状态。