编辑:所以似乎让方法返回void而不是任务意味着异常在错误的(意外的?)上下文中传播。 但是,我的IDE(Xamarin)仍然在我的构造函数中在线上大惊小怪,我调用了AttemptDatabseLoad()
“等待声明并执行当前方法 在通话结束前继续。考虑使用'await' 运算符或调用'等待'方法“
为什么要大惊小怪呢?当然,使用异步方法的整个目的正是为了让程序继续在主线程上执行。
我已经在异步上阅读了相当多的内容并等待,因为我需要为我正在制作的应用程序加载一些异步数据。我已经在很多地方读过,让异步方法返回void是不好的做法(除了触发事件的情况除外),我理解为什么保持对Task的处理是好的。 但是,我在下面写的内容中看不出任何逻辑上的错误,所以我的问题有两个:为什么我当前的代码执行不好?应该如何重写?
private const int MAX_CONNECTION_ATTEMPTS = 10;
private int ConnectionAttempts = 0;
//Constructor
public DataLoader()
{
//First load up current data from local sqlite db
LoadFromLocal();
//Then go for an async load from
AttemptDatabaseLoad();
}
public async void AttemptDatabaseLoad()
{
while(ConnectionAttempts < MAX_CONNECTION_ATTEMPTS){
Task<bool> Attempt = TryLoad ();
bool success = await Attempt;
if (success) {
//call func to load data into program memory proper
}else{
ConnectionAttempts++;
}
}
}
//placeholder for now
public async Task<bool> TryLoad()
{
await Task.Delay(5000);
return false;
}
答案 0 :(得分:4)
构造函数用于在初始化后将对象引入其完全构造的结构。另一方面,异步方法和构造函数不能很好地协同工作,因为构造函数是继承同步的。
解决此问题的方法通常是为类型公开一个初始化方法,该方法本身就是异步。现在,让调用者完全初始化对象。请注意,这将要求您监视方法的实际初始化。
当您需要比例时,异步会闪耀。如果您不希望这是应用程序中的IO瓶颈,可以考虑使用同步方法。这将为您提供在构造函数完成执行后实际完全初始化对象的好处。虽然,我不认为我会通过构造函数启动对数据库的调用:
slice()
并称之为:
public async Task InitializeAsync()
{
LoadFromLocal();
await AttemptDatabaseLoadAsync();
}
public async Task AttemptDatabaseLoadAsyncAsync()
{
while(ConnectionAttempts < MAX_CONNECTION_ATTEMPTS)
{
Task<bool> Attempt = TryLoad ();
bool success = await Attempt;
if (success)
{
//call func to load data into program memory proper
}
else
{
ConnectionAttempts++;
}
}
}
答案 1 :(得分:2)
我理解为什么保持对任务的处理是好的。
所以似乎让方法返回void而不是task意味着异常会在错误的(意外的?)上下文中传播。
拥有Task
的好处之一是您可以使用它来检索异步方法的结果。通过&#34;结果&#34;,我不仅仅意味着返回值 - 我的意思也是例外。 Task
表示该异步方法的执行。
当异常转义async Task
方法时,它会放在返回的任务上。当一个异常逃脱async void
方法时,它没有明显的位置,所以实际的行为是直接在SynchronizationContext
上提升它{ {1}}方法。这听起来很奇怪,但它专门用于模拟转义事件处理程序的异常。
当然,如果您的async void
方法不是事件处理程序(如此示例),则行为似乎非常奇怪且令人惊讶。
为什么要大惊小怪呢?当然,使用异步方法的整个目的正是为了让程序继续在主线程上执行。
我认为您误解了警告信息。由于async void
表示该方法的执行,因此忽略它是99.9%的错误。通过忽略它,您的代码表示,当异步方法完成时,它不关心,返回值是什么(如果有的话),以及是否它抛出例外。代码很少关心任何这些。
应如何重写?
我在how to do "async constructors"上发了一篇博文。我最喜欢的方法是异步工厂方法:
Task
但是,由于您在UI应用程序中使用它,我怀疑您最终会遇到要从ViewModel构造函数调用异步代码的情况。异步工厂非常适合帮助代码(如//Constructor
private DataLoader()
{
//First load up current data from local sqlite db
LoadFromLocal();
}
public static async Task<DataLoader> CreateAsync()
{
var result = new DataLoader();
await result.AttemptDatabaseLoadAsync();
return result;
}
),但它们不适用于ViewModel,因为需要立即创建 - UI需要显示某些内容现在
在UI层,您必须首先将您的UI初始化为某种类型的&#34; loading&#34;状态,然后更新它到&#34;正常&#34;一旦数据到达就表明状态。我更喜欢使用asynchronous data binding,如我的MSDN文章中所述。
答案 2 :(得分:1)
您可以将返回类型更改为任务(非通用),并且不要明确返回&#34;&#34;来自异步方法。可以在此处找到仅在顶级函数上使用void更好的原因:async/await - when to return a Task vs void? 因此,它主要是在async-void方法中从异常中恢复。我希望它会有所帮助。
编辑:还有一件事 - 因为我没有注意到你是从构造函数中调用它的。请检查此答案:https://stackoverflow.com/a/23051370/580207和此博文:http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
答案 3 :(得分:1)
为什么我目前的代码执行不佳?
DataLoader()
构造函数的来电者可能会遇到以下问题:
代码实例化DataLoader
类不知道DataLoader()
返回后加载操作仍在进行中,因此无法使用异步AttemptDatabaseLoad()
检索到的数据。
无法发现加载的数据何时可用。
建议的更改是将async方法返回的任务存储在属性中,以便调用者可以使用它等待加载完成,或者将其组合成异步方法。
class DataLoader
{
public DataLoader ()
{
//First load up current data from local sqlite db
LoadFromLocal();
//Then go for an async load from
this.Completion = AttemptDatabaseLoadAsync();
}
async Task AttemptDatabaseLoadAsync()
{
while(ConnectionAttempts < MAX_CONNECTION_ATTEMPTS){
Task<bool> Attempt = TryLoad ();
bool success = await Attempt;
if (success) {
//call func to load data into program memory proper
}else{
ConnectionAttempts++;
}
}
}
public Task Completion
{
get; private set;
}
}
用法:
var loader = new DataLoader();
loader.Completion.Wait();
或:
async Task SomeMethodAsync()
{
var loader = new DataLoader();
await loader.Completion;
}