2015-10-23 59 views
1

我遇到了一些行爲,讓我對使用垃圾收集器異步等待模式的行爲感到困惑。這裏是生產代碼的解釋。對象引用在多層異步等待操作中丟失

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這是一個虛假的肯定是真的在繼續。

+0

這非常需要一個MVCE。封裝在一個小的控制檯或WinForms應用程序。原因很可能在周圍的項目中。 –

回答

3

這與引用或垃圾收集無關。

您可以撥打AssignPrices,這是一種不等待的異步方法。這意味着你的手中有比賽條件。

異步方法將在調用線程上同步運行,直到達到第一個await並且該方法返回帶有代表該操作剩餘部分的任務的控制權。如果您不等待(或同步阻止)調用方法將與未等待的異步操作並行運行。

使用GC.KeepAlive只能延遲調用方法,而AssignPrices有機會完成。

您應該等待操作,並且只有在完成後才能繼續操作。您還應該添加「異步」後綴到異步方法,這有助於提醒您等待它。

public async Task CreateProductAsync(int id) 
{ 
    Product result = factory.Create(id); 
    await AssignPricesAsync(result); 
    Assert.That(result.Prices.Count == 1); 
} 
+0

對不起,我忘記了在僞代碼中的等待。我可以驗證在生產代碼中所有都在等待。 (這只是一些僞代碼,我扔在一起發佈在線) – BastanteCaro

+1

@BastanteCaro好吧,這段代碼看起來不錯。如果你有代碼重現問題,那麼你應該發佈它。 – i3arnon

+0

至於GC.KeepAlive,我的印象是它沒有阻塞,但通知GC線程不標記指定的對象進行收集。 – BastanteCaro