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

本文关键字:返回 一个 异步方法 为什么 可以避免 如何 | 更新日期: 2023-09-27 18:11:04

编辑:因此,似乎有方法返回void而不是任务意味着异常在错误的(意外的?)上下文上传播。然而,我的IDE (Xamarin)仍然在我的构造函数中调用attemptdatabselload ()

"该语句未被等待,当前方法正在执行在呼叫完成之前继续。考虑使用'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;
}

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

构造函数用于在初始化后将对象带入其完全构造的结构。另一方面,异步方法和构造函数不能很好地配合使用,因为构造函数本质上是同步的。

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

Async在你需要规模的时候很好用。如果您不希望这成为应用程序中的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();

我明白为什么保持Task的句柄是好的。

因此,似乎让方法返回void而不是task意味着异常在错误的(意外的?)上下文中传播。

使用Task的原因之一是您可以使用它来检索异步方法的结果。这里的"结果"并不仅仅指返回值——我也指异常。 Task表示异步方法的执行。

当异常转义async Task方法时,它被放置在返回的任务上。当异常转义async void方法时,它没有明显的去处,因此实际的行为是在async void方法开始时当前的SynchronizationContext上直接引发它。这听起来很奇怪,但它是专门设计来模拟异常转义事件处理程序的。

当然,如果您的async void方法不是事件处理程序(如本例),那么行为看起来非常奇怪和令人惊讶。

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

我想你误解了警告信息。由于Task表示该方法的执行,因此忽略它在99.9%的情况下是错误的。通过忽略它,您的代码表示它不关心何时 async方法完成,它的返回值是什么(如果有的话),以及它是否抛出异常。很少有代码不关心这些中的任何。

应该如何重写?

我有一篇关于如何做"异步构造函数"的博文。我最喜欢的方法是异步工厂方法:

//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应用程序中使用它,我怀疑您最终会遇到想要从ViewModel构造函数调用异步代码的情况。异步工厂对于辅助代码(如DataLoader)来说是很好的,但是对于ViewModels来说却行不通,因为vm需要立即创建—UI需要现在显示

在UI层,你必须首先将你的UI初始化为某种"加载"状态,然后更新为"正常"状态。一旦数据到达,就声明。我更喜欢使用异步数据绑定,正如我在MSDN文章中所描述的那样。

您可以将返回类型更改为Task(非泛型),并且不从async方法返回"显式"。为什么最好只在顶级函数上使用void的原因可以在这里找到:async/await -何时返回Task vs void?它主要是关于在async-void方法中从异常中恢复。我希望它会有所帮助。

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

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

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;
}
相关文章: