【问题标题】:Can we call an async method from a constructor? [duplicate]我们可以从构造函数中调用异步方法吗? [复制]
【发布时间】:2019-10-07 01:53:28
【问题描述】:

我需要从 mvc 应用程序调用第三方异步方法。此异步方法的名称是 ForceClient.QueryAsync。它来自一个开源项目:https://github.com/developerforce/Force.com-Toolkit-for-NET/。

以下工作正常,当进程处于 mvc 的 View 阶段时,model.Opportunity 包含预期的信息:

public async Task<ActionResult> MyController(string Id) {
    . . . .
    MyModel model = new MyModel();
    var client = new ForceClient(instanceUrl, accessToken, apiVersion);
    var qry = await client.QueryAsync<MyModel.SFOpportunity>(
            "SELECT Name, StageName FROM Opportunity where  Id='" + Id + "'");
    model.Opportunity = qry.Records.FirstOrDefault();
    . . . .
return View(viewName, myModel);
}

但下面不起作用。当流程处于 View 阶段时,model.Opportunity 为 null。我做了一些调试,发现流程是这样的:

1) 步骤1

2) 第二步

3) 在视图阶段。此时 model.Opportunity 为 null,我需要填充它。

4) 第三步。

public async Task<ActionResult> MyController(string Id) {
    . . . .
    MyModel myModel = await Task.Run(() =>
        {
            var result = new MyModel(Id);
            return result;

        });   // =====> Step 1
    . . . .
return View(viewName, myInfoView);
}    

public class MyModel
{
    public SFOpportunity Opportunity { get; set; }
    public MyModel(string id)
    {
        setOpportunityAsync(id);
    }

    private async void setOpportunityAsync(string id)
    {
        . . .
        var client = new ForceClient(instanceUrl, accessToken, apiVersion);
        var qry = await client.QueryAsync<MyModel.SFOpportunity>(
             "SELECT Name, StageName FROM Opportunity where  Id='" + id + "'");  // ======>  Step2 
        Opportunity = qry.Records.FirstOrDefault();  // =====> step3
    }

所以,我的问题是我需要做什么才能让它按以下顺序执行步骤: 1) 第一步

2) 第二步

3) 第三步

4) 在视图阶段。此时应该填充 model.Opportunity。

【问题讨论】:

  • 我认为应该可以。您确定 select 语句返回结果吗?

标签: c#


【解决方案1】:

MyModel 的构造函数不会(也不能)等待setOpportunityAsync,因为构造函数本身不是(也不能是)异步的。否则,您将能够等待对构造函数本身的调用,但您不能。因此,在调用构造函数后,异步方法可能不会立即完成执行。它会完成的……只要它完成了。

这是一个较小的测试类来说明行为:

public class HasConstructorWithAsyncCall
{
    public HasConstructorWithAsyncCall()
    {
        MarkConstructorFinishedAsync();
    }

    public bool ConstructorHasFinished { get; private set; }

    async void MarkConstructorFinishedAsync()
    {
        await Task.Delay(500);
        ConstructorHasFinished = true;
    }
}

在构造实例后,ConstructorHasFinished 的值是多少?这是一个单元测试:

[TestMethod]
public void TestWhenConstructorFinishes()
{
    var subject = new HasConstructorWithAsyncCall();
    Assert.IsFalse(subject.ConstructorHasFinished);
    Thread.Sleep(600);
    Assert.IsTrue(subject.ConstructorHasFinished);
}

测试通过。构造函数在MarkConstructorFinishedAsync 尚未完成时返回,因此ConstructorHasFinished 为假。半秒后结束,值为真。

你不能标记构造函数async,所以你不能await构造函数中的任何东西。

一般来说,我们不会将任何像数据检索这样的长时间运行的东西放在构造函数中,包括我们会异步调用的任何东西。如果我们这样做了,那么我们必须要么同步调用它,要么知道构造函数的完成并不意味着它已经完全“构造好了”。

【讨论】:

  • 是的,如果我更改我的代码以同步调用它,它工作正常。我试图避免对异步方法进行同步调用。这有什么不好的地方吗?我自己不认为数据检索应该被视为长期运行的过程。大多数基于交易的网站都需要随时访问数据库中的数据。随着服务的增加,现在很多网站都需要执行 Web api 调用来应用构建页面所需的业务逻辑。
【解决方案2】:

你不能有 async 构造函数。

One alternative is to have async factory methods:

public class MyModel
{
  public SFOpportunity Opportunity { get; set; }
  private MyModel() { }

  public static async Task<MyModel> CreateAsync(string id)
  {
    var result = new MyModel();
    await result.setOpportunityAsync(id);
    return result;
  }

  private async Task setOpportunityAsync(string id)
  {
    ...
  }
}

【讨论】:

  • 我是 C# 新手,尤其是异步。但是异步工作的方式对我来说似乎有问题。所以,让我们假设我有一个现有的应用程序,其中数据库和业务逻辑层深埋在应用程序中,并且它们当前都没有处理任何异步调用。如果有新需求出现,我需要更改这些层中的方法以现在进行异步 Web api 调用,这是否意味着该方法上方的调用链中的每个方法都需要更改以处理异步/await?同步异步调用会是这种情况的解决方案吗?
  • @HockChaiLim:调用链中的每个方法都应该是async。任何其他解决方案都是有问题的。
猜你喜欢
  • 2019-12-11
  • 2019-08-18
  • 2014-02-16
  • 2011-12-30
  • 1970-01-01
  • 2013-02-25
相关资源
最近更新 更多