我遇到了一些行为,这些行为让我对垃圾收集器的异步等待模式的行为感到有些困惑。这是对生产代码的解释。
public async Task CreateProduct(int id)
{
Product result = factory.Create(id);
await AssignPrices(result);
GC.KeepAlive(result);
Assert.That(result.Prices.Count == 1); //this is true
}
public async Task AssignPrices(Product value)
{
foreach (var engine in pricingEngines)
{
await engine.AddPrice(value);
}
}
public class DefaultPricingEngine
{
public async Task AddPrice(Product value)
{
var price = await _externalApi.GetPrice();
value.Prices.Add(price);
}
}
class Product
{
public int Id{get;set;}
public string Name {get;set;}
public List<decimal> Prices {get;set;}
}
如果我省略 GC.KeepAlive
,它似乎会恢复到最初创建的产品。好像我保留它一样,价格按预期添加。
这里到底发生了什么导致清理.. GC.KeepAlive
是否真的发生了误报。
最佳答案
这与引用或垃圾收集无关。
您调用 AssignPrices
,这是一个异步方法,无需等待它。这意味着您手上有竞争条件。
async 方法将在调用线程上同步运行,直到到达第一个 await 并且该方法返回控制权以及代表操作其余部分的任务。如果您不等待它(或同步阻止它),调用方法将与未等待的异步操作并行运行。
使用 GC.KeepAlive
只能通过延迟调用方法来实现,而 AssignPrices
有机会完成。
您应该等待操作,只有在操作完成后才能继续。您还应该向异步方法添加“Async”后缀,这有助于提醒您等待它。
public async Task CreateProductAsync(int id)
{
Product result = factory.Create(id);
await AssignPricesAsync(result);
Assert.That(result.Prices.Count == 1);
}
关于c# - 对象引用在多层异步等待操作中丢失,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33306490/