我有一个项目,我试图在构造函数中填充一些数据:
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
。可能有很多(甚至是明显的)边缘案例和反对它的原因,我只是想不出。我也四处寻找解释,但似乎找不到任何解释。
async constructor pattern
。
由于无法创建异步构造函数,因此我使用了一个静态异步方法,该方法返回由私有构造函数创建的类实例。这并不优雅,但可以正常工作。
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;
}
}
构造函数的行为与返回构造类型的方法非常相似。而且 async
方法不能只返回任何类型,它必须是“即发即弃”void
或 Task
。
如果 T
类型的构造函数实际上返回了 Task<T>
,我认为这将非常令人困惑。
如果异步构造函数的行为方式与 async void
方法相同,那么就会破坏构造函数的本意。构造函数返回后,你应该得到一个完全初始化的对象。不是将来会在某个未定义的点实际正确初始化的对象。也就是说,如果你很幸运并且异步初始化没有失败。
这一切都只是猜测。但在我看来,拥有异步构造函数的可能性带来的麻烦多于其价值。
如果您确实想要 async void
方法的“即发即弃”语义(应尽可能避免),您可以轻松地将所有代码封装在一个 async void
方法中并从您的构造函数中调用它,正如您在这个问题。
await
可以经常替换 .ContinueWith
,以至于我很容易忘记它并不是那么简单。我什至不确定我在想什么,但我想我认为 await
应该“返回”一个构造的 T
(你指出这不是异步方法可以返回的),因为那是构造函数“返回”,但是当等待继续时,构造函数不会返回任何内容,因为它是一个构造函数,例如 void
。我什至没有意义了,但你的回答是最有帮助的。谢谢。
您的问题类似于创建文件对象并打开文件。事实上,在很多类中,您必须执行两个步骤才能真正使用对象:创建 + 初始化(通常称为类似于 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);
}
async constructor pattern
。 - IMO,这应该是公认的答案,因为它很好,简单,而且很重要 - 干得好!
Type 'MyClassViewModel' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.
如果使构造函数异步,则在创建对象后,您可能会遇到空值而不是实例对象等问题。例如;
MyClass instance = new MyClass();
instance.Foo(); // null exception here
这就是我猜他们不允许这样做的原因。
readonly
关键字做出的保证。
在这种特殊情况下,需要一个 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 是受支持的。 :)
我只是想知道为什么我们不能直接从构造函数中调用 await 。
我相信简短的回答很简单:因为 .Net 团队尚未编写此功能。
我相信使用正确的语法可以实现这一点,并且不应该太混乱或容易出错。我认为 Stephen Cleary 的 blog post 和这里的其他几个答案已经含蓄地指出,没有根本的理由反对它,而且更重要的是 - 通过变通方法解决了这一缺陷。这些相对简单的变通方法的存在可能是此功能尚未(尚未)实现的原因之一。
在构造函数中调用 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
async
方法(这是可能的,但可能不是一个好主意)。这个问题是关于 constructor 本身 是 async
(根本不会编译)。
一些答案涉及创建一个新的 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
构造函数的要求。
使用 Lazy
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);
}
您可以使用方法而不是属性公开数据
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();
}
使用在使用实例之前必须调用的 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();
}
通过将昂贵的参数传递给构造函数来实例化实例
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 };
}
使用工厂方法和私有构造函数
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();
}
您可以在构造函数中使用 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;
}
}
async void
方法,这不是一个好主意。
Result
可能会导致死锁。我有多种解决方案described on my blog。
请提出这个语言请求:
https://github.com/dotnet/csharplang/discussions/419
每个人都需要编写大量样板代码才能拥有一个完全初始化的异步对象,这与 C# 中的趋势完全相反(样板更少)。
您可以创建一个包装器并注入一个代表构造函数的函子:
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;
}
}
我会使用这样的东西。
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 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
*/
}
}
我不熟悉 async 关键字(这是 Silverlight 特有的还是 Visual Studio 测试版中的新功能?),但我想我可以告诉您为什么不能这样做。
如果我做:
var o = new MyObject();
MessageBox(o.SomeProperty.ToString());
o 在下一行代码运行之前可能无法完成初始化。在构造函数完成之前无法分配对象的实例化,并且使构造函数异步不会改变这一点,那么重点是什么?但是,您可以从构造函数调用异步方法,然后构造函数可以完成,并且您将获得实例化,而异步方法仍在执行设置对象所需的任何操作。
Task<MyObject>()
。