构造函数是异步的吗?

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

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

283245 次浏览

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

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

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

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

如果我这样做:

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

在运行下一行代码之前,O可能无法完成初始化。在构造函数完成之前,不能分配对象的实例化,并且使构造函数异步不会改变这一点,那么重点是什么呢?然而,你可以从你的构造函数调用一个异步方法,然后你的构造函数就可以完成,你就可以在async方法仍然在做它需要做的事情来设置你的对象的时候得到你的实例化。

构造函数的作用与返回构造类型的方法非常相似。并且async方法不能返回任意类型,它必须是“fire And forget”voidTask

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

如果async构造函数的行为方式与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;
}
}

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

在这种特殊情况下,需要一个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。:)

您的问题类似于创建文件对象并打开文件。事实上,有很多类在实际使用对象之前必须执行两个步骤: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);
}

我会用这样的东西。

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

这是我能得到的最接近构造函数的值。

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

我相信简短的答案很简单:因为. net团队还没有编写这个特性。

我相信使用正确的语法可以实现这一点,而且不应该太令人困惑或容易出错。我认为Stephen Cleary的博客和其他几个答案已经含蓄地指出,没有根本的理由反对它,而且更重要的是-解决了解决方案的不足。这些相对简单的变通方法的存在可能是这个特性(还)没有实现的原因之一。

其中一些答案涉及到创建一个新的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

c#不允许async构造函数。构造函数应该在简单的初始化后快速返回。你不期望也不想等待一个实例,即构造函数返回。因此,即使可以使用异步构造函数,构造函数也不是长期运行操作或启动后台线程的地方。构造函数的唯一目的是将实例或类成员初始化为默认值或捕获的构造函数参数。你总是创建实例,然后在这个实例上调用DoSomething()。异步操作也不例外。你总是推迟昂贵的成员初始化。

有一些解决方案可以避免async构造函数的要求。

  1. 使用Lazy<T>AsyncLazy<T>的简单替代解决方案(需要通过NuGet包管理器安装Microsoft.VisualStudio.Threading包)。Lazy<T>允许延迟昂贵资源的实例化或分配。
public class OrderService
{
public List<object> Orders => this.OrdersInitializer.GetValue();
private AsyncLazy<List<object>> OrdersInitializer { get; }


public OrderService()
=> this.OrdersInitializer = new AsyncLazy<List<object>>(InitializeOrdersAsync, new JoinableTaskFactory(new JoinableTaskContext()));


private async Task<List<object>> InitializeOrdersAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
return new List<object> { 1, 2, 3 };
}
}


public static void Main()
{
var orderService = new OrderService();


// Trigger async initialization
orderService.Orders.Add(4);
}
  1. 可以使用方法而不是属性公开数据
public class OrderService
{
private List<object> Orders { get; set; }


public async Task<List<object>> GetOrdersAsync()
{
if (this.Orders == null)
{
await Task.Delay(TimeSpan.FromSeconds(5));
this.Orders = new List<object> { 1, 2, 3 };
}
return this.Orders;
}
}


public static async Task Main()
{
var orderService = new OrderService();


// Trigger async initialization
List<object> orders = await orderService.GetOrdersAsync();
}
  1. 使用InitializeAsync方法,必须在使用实例之前调用该方法
public class OrderService
{
private List<object> orders;
public List<object> Orders
{
get
{
if (!this.IsInitialized)
{
throw new InvalidOperationException();
}
return this.orders;
}
private set
{
this.orders = value;
}
}


public bool IsInitialized { get; private set; }


public async Task<List<object>> InitializeAsync()
{
if (this.IsInitialized)
{
return;
}


await Task.Delay(TimeSpan.FromSeconds(5));
this.Orders = new List<object> { 1, 2, 3 };
this.IsInitialized = true;
}
}


public static async Task Main()
{
var orderService = new OrderService();


// Trigger async initialization
await orderService.InitializeAsync();
}
  1. 通过将昂贵的参数传递给构造函数来实例化实例
public class OrderService
{
public List<object> Orders { get; }


public async Task<List<object>> OrderService(List<object> orders)
=> this.Orders = orders;
}


public static async Task Main()
{
List<object> orders = await GetOrdersAsync();


// Instantiate with the result of the async operation
var orderService = new OrderService(orders);
}


private static async Task<List<object>> GetOrdersAsync()
{
await Task.Delay(TimeSpan.FromSeconds(5));
return new List<object> { 1, 2, 3 };
}
  1. 使用工厂方法和私有构造函数
public class OrderService
{
public List<object> Orders { get; set; }


private OrderServiceBase()
=> this.Orders = new List<object>();


public static async Task<OrderService> CreateInstanceAsync()
{
var instance = new OrderService();
await Task.Delay(TimeSpan.FromSeconds(5));
instance.Orders = new List<object> { 1, 2, 3 };
return instance;
}
}


public static async Task Main()
{
// Trigger async initialization
OrderService orderService = await OrderService.CreateInstanceAsync();
}

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

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

请点击此语言请求:

https://github.com/dotnet/csharplang/discussions/419

为了拥有一个完全初始化的async对象,每个人都需要编写大量的样板代码,这与c#的趋势完全相反(样板代码更少)。