393

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

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直接从构造函数中调用。可能有很多(甚至是明显的)边缘案例和反对它的原因,我只是想不出。我也四处寻找解释,但似乎找不到任何解释。

4

12 回答 12

396

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

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;   
    }
}  
于 2012-09-20T20:39:23.323 回答
257

构造函数的行为与返回构造类型的方法非常相似。并且async方法不能只返回任何类型,它必须是“一劳永逸” void,或者Task.

我认为,如果 type 的构造函数T实际返回Task<T>,那将非常令人困惑。

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

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

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

于 2011-11-16T01:49:40.487 回答
78

您的问题类似于创建文件对象并打开文件。事实上,在很多类中,您必须执行两个步骤才能真正使用对象:创建 + 初始化(通常称为类似于 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);
}
于 2015-07-17T08:56:00.997 回答
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 是受支持的。:)

于 2014-12-31T18:53:36.840 回答
3

如果使构造函数异步,则在创建对象后,您可能会遇到空值而不是实例对象等问题。例如;

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

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

于 2011-11-16T01:28:51.017 回答
3

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

我相信简短的回答很简单:因为 .Net 团队尚未编写此功能。

我相信使用正确的语法可以实现这一点,并且不应该太混乱或容易出错。我认为 Stephen Cleary 的博客文章和这里的其他几个答案已经含蓄地指出,没有根本的理由反对它,而且更重要的是 - 通过变通方法解决了这一问题。这些相对简单的变通方法的存在可能是此功能尚未(尚未)实现的原因之一。

于 2017-05-03T19:55:25.147 回答
1

在构造函数中调用 async 可能会导致死锁,请参考 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

于 2012-11-07T21:54:35.433 回答
1

一些答案涉及创建一种新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

于 2019-02-27T02:37:22.740 回答
-1

您可以在构造函数中使用 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;
        }
    }
于 2013-03-20T08:55:35.960 回答
-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;
               }
    }

这与我可以为构造函数获得的一样接近。

于 2017-04-17T19:03:05.123 回答
-4

我使用这个简单的技巧。

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
    */
  }
}
于 2013-09-06T15:19:51.923 回答
-5

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

如果我做:

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

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

于 2011-11-16T01:29:29.220 回答