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

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。可能有很多(甚至明显的)边缘情况和理由反对它,我只是想不出任何一个。我也四处寻找解释,但似乎找不到任何解释。


当前回答

你可以创建一个包装器并注入一个表示构造函数的函子:

class AsyncConstruct<T>
    where T: class
{
    private readonly Task<T> m_construction;
    private T m_constructed;
    public AsyncConstruct(Func<T> createFunc)
    {
        m_constructed = null;
        m_construction = Task.Run(()=>createFunc());
    }

    public T Get()
    {
        if(m_constructed == null)
        {
            m_constructed = m_construction.Result;
        }
        return m_constructed;
    }
}

其他回答

在这种特殊情况下,需要一个viewModel来启动任务,并在任务完成时通知视图。一个“async属性”,而不是一个“async构造函数”。

我刚刚发布了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。:)

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

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

我猜这就是他们不允许这样做的原因。

你可以在构造函数中使用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;
        }
    }

我用这个简单的技巧。

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

您的问题类似于创建文件对象并打开文件。事实上,有很多类在实际使用对象之前必须执行两个步骤:create + Initialize(通常称为类似于Open的东西)。

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

缺点是您必须相信类的用户在使用类的任何其他函数之前会调用Initialize()。事实上,如果你想让你的类完全证明(傻瓜证明?),你必须检入Initialize()被调用的每个函数。

简化此操作的模式是将构造函数声明为私有,并创建一个公共静态函数,该函数将构造对象,并在返回构造的对象之前调用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
    }

使用方法如下:

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