C# 构造函数可以是异步的吗?

C# 构造函数可以是异步的吗?,c#,constructor,async-await,C#,Constructor,Async Await,我有一个项目,试图在构造函数中填充一些数据: public class ViewModel { public ObservableCollection<TData> Data { get; set; } async public ViewModel() { Data = await GetDataTask(); } public Task<ObservableCollection<TData>> Ge

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

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;
    }
}
public async void Foo()
{
    Data = await GetDataTask();
}
它很好用。同样,如果我使用旧的由内而外的方式

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

这也行。我只是想知道为什么我们不能直接从构造函数中调用
wait
。可能有很多(甚至是明显的)边缘案例和反对它的理由,我想不出任何理由。我也在四处寻找解释,但似乎找不到任何解释。

如果在创建对象后使构造函数异步,可能会遇到空值而不是实例对象之类的问题。比如,

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

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

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

如果我这样做:

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

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

构造函数的行为与返回构造类型的方法非常相似。而且
async
方法不能返回任何类型,它必须是“激发并忘记”
void
,或者
任务

如果类型为
T
的构造函数实际返回了
Task
,我认为这将非常混乱

如果异步构造函数的行为方式与
async void
方法相同,那么这种类型的构造函数将破坏构造函数的本意。构造函数返回后,您应该得到一个完全初始化的对象。不是一个在将来某个未定义的点上被正确初始化的对象。也就是说,如果幸运的话,异步初始化没有失败

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


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

因为不可能生成异步构造函数,我使用一个静态异步方法,它返回一个由私有构造函数创建的类实例。这并不优雅,但它的工作正常

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;   
    }
}  
公共类视图模型
{       
公共可观测收集数据{get;set;}
//行为类似于构造函数的静态异步方法
异步公共静态任务BuildViewModelAsync()
{       
ObservableCollection tmpData=等待GetDataTask();
返回新的ViewModel(tmpData);
}       
//异步方法调用的私有构造函数
私有视图模型(可观察的采集数据)
{
这个。数据=数据;
}
}  

在构造函数中调用async可能会导致死锁,请参阅


您可以在构造函数中使用操作

 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;
        }
    }
公共类视图模型
{
公共可观测收集数据{get;set;}
公共视图模型()
{              
新操作(异步()=>
{
数据=等待GetDataTask();
}).Invoke();
}
公共任务GetDataTask()
{
任务;
//创建表示获取数据的任务
返回任务;
}
}
我使用这个简单的技巧

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

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

我刚刚发布了,它正好解决了这个问题。如果使用它,您的ViewModel将成为:

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

    private Task<ObservableCollection<TData>> GetDataAsync()
    {
        //Get the data asynchronously
    }
}
公共类ViewModel:AsyncBindableBase
{
公共可观测收集数据
{
get{return Property.get(GetDataAsync);}
}
私有任务GetDataAsync()
{
//异步获取数据
}
}

奇怪的是,Silverlight得到了支持。:)

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

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

缺点是您必须信任类的用户,在他使用类的任何其他函数之前,他将调用
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
    }
公共MyClass
{
公共静态异步任务CreateAsync(…)
{
MyClass x=新的MyClass();
等待x.InitializeAsync(…)
返回x;
}
//确保只有Create函数才能调用构造函数:
私人MyC
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);
}
 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;
               }
    }
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();
    }
}