2015-09-04 62 views
3

编辑:所以它似乎有方法返回void而不是任务意味着异常传播在错误(意外的?)上下文。 然而,我的IDE(Xamarin)仍然在我的构造函数)行,我叫AttemptDatabseLoad(如何(以及为什么)可以避免在这些异步方法上返回void?

“的说法是不是等待和当前方法 的继续执行被调用完成前踢了大惊小怪。考虑使用'await' 运营商或致电'等待'方法“

为什么它踢这个大惊小怪?当然,使用异步方法的全部目的正是为了让程序在主线程上继续执行。

我读过一段关于异步的内容并等待,因为我需要为正在制作的应用程序加载一些异步数据。我读过大量的地方,认为异步方法返回void(除了触发事件的情况)是不好的做法,并且我理解为什么可以很好地处理任务。 但是,我看不出有什么逻辑错误,所以我的问题是双重的:为什么我现在的代码很差?它应该如何重写?

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

在构造函数中的异步调用并没有多大意义 - 新的对象不仅是半未初始化的,你甚至不知道*当*这将是安全的使用。 –

+0

我很困惑。毫无疑问,每当等待被调用时,执行会分裂成一个新的上下文,并继续在主要的上下文中执行?所以AttemptDatabaseLoad()不应该阻止主线程(或调用)线程,因为对象构造将像往常一样快。 –

+0

不,它不会阻止*这是一个问题。你会得到一个对象,可能包含或不包含有效的数据,但你不会有任何的方式知道。你甚至不知道你需要等多久才能使用该对象。尤瓦的回答和克里姆的答案的链接解释热避免这种 –

回答

4

构造是为了把一个对象到它的完全构造结构初始化一次。另一方面,异步方法和构造函数不能很好地发挥作用,因为构造函数是继承同步的。

解决此问题的方法通常是公开类型的初始化方法,该方法本身是异步的。现在,你让调用者完全初始化对象。请注意,这将要求您监视方法的实际初始化。

当您需要缩放时,异步闪烁。如果您不希望这会成为应用程序中的IO瓶颈,那么可以考虑使用同步方法。一旦构造器完成执行,这会给你实际完全初始化对象的好处。虽然,我不认为我会通过构造函数初始化到数据库的调用反正:

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

,并称之为:

var dataLoader = new DataLoader(); 
await dataLoader.InitializeAsync(); 
1

您可以将返回类型更改为任务(非泛型),并且不要从异步方法“显式”返回。为什么只在顶层函数中使用void更好的原因可以在这里找到:async/await - when to return a Task vs void? 所以,它主要是从异常中恢复异步无效的方法。我希望这会有所帮助。

编辑:还有一件事 - 因为我没有注意到你从构造函数调用它。请检查此答案:https://stackoverflow.com/a/23051370/580207和此博客文章:http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html

1

为什么我现在的代码很差的做法?

呼叫者DataLoader()构造可能会遇到以下问题的

  • 代码实例DataLoader类是不知道该加载作业仍在进行中后DataLoader()回报,所以它不能使用数据通过异步AttemptDatabaseLoad()检索。

  • 没有办法发现加载的数据何时可用。

  • 它不能组成一个更大的异步操作。

建议的更改是将异步方法返回的任务存储在属性中,以便调用方可以使用它等待直到加载完成,或者将其组合为异步方法。

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

你第一次使用会死锁)可能结束,因为等待(会阻塞线程。你的第二个用法是使用。 – GazTheDestroyer

+0

@GazTheDestroyer:嗯,它从UI线程调用确实是可能的。会添加'ConfigureAwait(false)'到_await_ _Attempt_减轻死锁问题吗? – alexm

2

我理解的原因,它可以很好地保持对任务的处理。

所以它似乎让方法返回void而不是任务意味着异常在错误的(意外的?)上下文中传播。

拥有Task的原因之一就是您可以使用它来检索异步方法的结果。而通过“结果”,我不只是指回报价值 - 我也指例外情况。 Task代表该异步方法的执行。

当异常转义async Task方法时,它将放置在该返回的任务上。当一个异常脱离async void方法时,它没有明显的位置,所以实际行为是直接在async void方法开始时的SynchronizationContext上提升它。这听起来很奇怪,但它专门用来模拟异常转义事件处理程序。

当然,如果您的async void方法不是事件处理程序(如此示例),那么行为似乎很奇怪和令人惊讶。

为什么它踢这个大惊小怪?当然,使用异步方法的全部目的正是为了让程序在主线程上继续执行。

我想你是误解了警告信息。由于Task代表该方法的执行,所以忽略它是99.9%的错误。通过忽略它,你的代码说它不在意当异步方法完成,它的返回值是(如果有),并且它是否抛出例外。对于代码来说,这是非常罕见的,它们不关心的任何

究竟应该如何改写?

我在how to do "async constructors"上有博客文章。我最喜欢的方法是异步的工厂方法:

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

但是,因为你在一个UI应用程序中使用此,我怀疑你最终会碰到,你想从您的视图模型构造函数中调用异步代码的情况。异步工厂非常适合帮助代码(如DataLoader),但它们不适用于ViewModels,因为需要立即创建虚拟机 - 用户界面需要显示的内容现在为

在UI层,你有你的UI首先初始化到某种“加载”状态,然后更新到一个“正常”状态,一旦数据已经到达。正如我的MSDN文章中所述,我更愿意使用asynchronous data binding

相关问题