如何(以及为什么)我可以避免在这些异步方法上返回空格?

时间:2015-09-04 06:56:01

标签: c# asynchronous async-await

编辑:所以似乎让方法返回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;
}

4 个答案:

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