2014-09-30 86 views
6

我用下面的方法來異步執行一些任務,並在同一時間:異步任務是兩次評估

public async Task<Dictionary<string, object>> Read(string[] queries) 
{ 
    var results = queries.Select(query => new Tuple<string, Task<object>>(query, LoadDataAsync(query))); 

    await Task.WhenAll(results.Select(x => x.Item2).ToArray()); 

    return results 
     .ToDictionary(x => x.Item1, x => x.Item2.Result); 
} 

我想要的方法來調用LoadDataAsync用於在同一時間陣列中的每個字符串,然後等到所有任務完成並返回結果。

  • 如果我像這樣運行的方法,它在最後.Result屬性的getter調用LoadDataAsync兩次,每次項目,一旦在await ...線,並一次。
  • 如果我刪除await ...行,Visual Studio會警告說整個方法可以並行運行,因爲方法內部沒有調用await

我在做什麼錯?

有沒有更好(更短)的方法來做同樣的事情?

+3

嗯,當然他們是 - 你在枚舉結果查詢兩次。如果首先使用'queries ... ToArray()',它將起作用。你枚舉兩次'queries',每次都做一個新的'LoadDataAsync'。 – Luaan 2014-09-30 15:38:16

+1

我對你的代碼示例有些困惑。數組'items'被忽略,並使用一個叫做'queries'的神祕全局變量。你發佈了一個不完整的代碼示例嗎? – 2014-09-30 15:46:48

+0

是的,對不起。更正它。不應該在SO內進行代碼編輯。 – cheeesus 2014-09-30 17:16:01

回答

23

如果我能教人們關於LINQ的一件事情,那就是查詢的值是執行查詢的對象,而不是執行查詢的結果。

您創建查詢一次,生成一個可以執行查詢的對象。然後執行兩次查詢。您不幸地創建了一個查詢,該查詢不僅計算值,而且還產生副作用,因此執行查詢兩次會產生副作用兩次。 不要使產生副作用的可重複使用的查詢對象永遠爲。查詢是詢問問題的機制,因此它們的名稱。它們並不打算成爲一種控制流程機制,但這正是您使用它們的原因。

執行查詢兩次會產生兩個不同的結果,因爲當然查詢的結果可能在兩次執行之間發生了變化。如果查詢正在查詢數據庫,比方說,數據庫可能在執行過程中發生了變化。如果您的查詢是「倫敦每個客戶的姓氏都是什麼?」答案可能是從毫秒改爲毫秒,但問題保持不變。請記住,查詢代表

我會傾向於寫一些沒有疑問的東西。使用「foreach」循環來創建副作用。

public async Task<Dictionary<string, object>> Read(IEnumerable<string> queries) 
{ 
    var tasks = new Dictionary<string, Task<object>>(); 
    foreach (string query in queries) 
     tasks.Add(query, LoadDataAsync(query)); 
    await Task.WhenAll(tasks.Values); 
    return tasks.ToDictionary(x => x.Key, x => x.Value.Result); 
} 
3

一個更好的辦法可能是格式化這樣的方式的任務的結果是由等待着他們各自的鍵返回的異步調用:再次

public async Task<KeyValuePair<string, object>> LoadNamedResultAsync(string query) 
{ 
    object result = null; 
    // Async query setting result 
    return new KeyValuePair<string, object>(query, result) 
} 

public async Task<IDictionary<string, object>> Read(string[] queries) 
{ 
    var tasks = queries.Select(LoadNamedResultAsync); 
    var results = await Task.WhenAll(tasks); 
    return results.ToDictionary(r => r.Key, r => r.Value); 
} 
8

你必須記住,LINQ操作返回查詢,而不是這些查詢的結果。變量results並不代表您擁有的操作的結果,而是代表查詢在迭代時能夠生成這些結果。你迭代它兩次,在每個場合執行查詢。

您可以在此處執行的操作是將查詢的結果首先物化爲集合,而不是將查詢本身存儲在results中。

var results = queries.Select(query => Tuple.Create(query, LoadDataAsync(query))) 
    .ToList(); 

await Task.WhenAll(results.Select(x => x.Item2)); 

return results 
    .ToDictionary(x => x.Item1, x => x.Item2.Result); 
0

爲補充Jesse Sweetland's答案,將完全實現的版本:

public async Task<KeyValuePair<string, object>> LoadNamedResultAsync(string query) 
{ 
    Task<object> getLoadDataTask = await LoadDataAsync(query); 
    return new KeyValuePair<string, object>(query, getLoadDataTask.Result); 
} 

public async Task<IDictionary<string, object>> Read(string[] queries) 
{ 
    var tasks = queries.Select(LoadNamedResultAsync); 
    var results = await Task.WhenAll(tasks); 
    return results.ToDictionary(r => r.Key, r => r.Value); 
} 

REM:我提出這個作爲編輯,但正是因爲太多的變化拒絕。