2015-06-17 150 views
3

可能此問題已經提出,但我從未找到權威答案。假設我有一個Web API 2.0託管在IIS上的應用程序。我想我理解最佳實踐(防止客戶端出現死鎖)是總是使用從GUI事件到HttpClient調用的異步方法。這是好的,它的工作原理。但是,如果我的客戶端應用程序沒有GUI(例如Window Service,Console Application),但是只有同步方法可以從中撥打電話,那麼最佳做法是什麼?在這種情況下,我用下面的邏輯:Web API同步呼叫最佳實踐

void MySyncMethodOnMyWindowServiceApp() 
{ 
    list = GetDataAsync().Result().ToObject<List<MyClass>>(); 
} 

async Task<Jarray> GetDataAsync() 
{ 
    list = await Client.GetAsync(<...>).ConfigureAwait(false); 
    return await response.Content.ReadAsAsync<JArray>().ConfigureAwait(false); 
} 

但不幸的是這可能仍然導致死鎖在客戶端上隨機計算機出現在隨機時間。

的客戶端應用程序停止在這一點上和從不返回:

list = await Client.GetAsync(<...>).ConfigureAwait(false); 
+0

它看起來像這可能是阻止 - >列表= GetDataAsync()。結果() –

+0

客戶端或服務器上的死鎖? –

+0

嘗試返回結果而不是等待。 'await'並返回 –

回答

1

使用Task<T>.ResultWait相當於其將在線程上執行同步塊。在WebApi上使用異步方法,然後讓所有調用者同步阻止它們,使WebApi方法同步。在負載下,如果同時等待的數量超過服務器/應用程序線程池,則會導致死鎖。

因此,請記住經驗法則「異步一直向下」。您希望長時間運行的任務(獲取List的集合)是異步的。如果調用方法必須是同步的,那麼您希望從異步到同步(使用Result或Wait)進行儘可能接近「地」的轉換。讓他們長時間運行異步進程並使同步部分儘可能短。這將大大減少線程被阻塞的時間長度。

因此,例如,你可以做這樣的事情。

void MySyncMethodOnMyWindowServiceApp() 
{ 
    List<MyClass> myClasses = GetMyClassCollectionAsync().Result; 
} 

Task<List<MyClass>> GetMyListCollectionAsync() 
{ 
    var data = await GetDataAsync(); // <- long running call to remote WebApi? 
    return data.ToObject<List<MyClass>>(); 
} 

關鍵部分是長時間運行的任務保持異步並且未被阻止,因爲使用了await。

也不要混淆響應性和可伸縮性。兩者都是異步的有效原因。 Yes響應是使用異步的原因(以避免在UI線程上阻塞)。你是對的,這不適用於後端服務,但這不是爲什麼在WebApi上使用異步。 WebApi也是一個非GUI後端進程。如果異步代碼的唯一優點是UI層的響應性,那麼WebApi將從頭到尾都是同步代碼。使用異步的另一個原因是可伸縮性(避免死鎖),這就是爲什麼WebApi調用是垂直異步的原因。保持長時間運行的進程異步可幫助IIS更高效地使用有限數量的線程。默認情況下,每個核心只有12個工作線程。這可以提出,但這不是一個神奇的子彈,因爲線程相對昂貴(每個線程大約1MB的開銷)。等待讓你用更少的錢做更多的事。發生死鎖之前,在較少線程上執行更多併發的長時間運行進程。

+0

@Riccardo這解決了你的問題? –

+0

我想用你的例子,但我如何使用await在一個方法是不是異步?謝謝 – Riccardo

+0

我得到編譯器錯誤:「等待運算符只能在一個異步方法中使用」 – Riccardo

0

您遇到的死鎖問題必須由其他問題引起。您在這裏使用ConfigureAwait(false)可以防止死鎖。解決這個錯誤,你很好。

查看Should we switch to use async I/O by default?答案是「否」。您應該根據具體情況決定並在收益超過成本時選擇異步。瞭解異步IO具有與其相關的生產力成本是很重要的。在非GUI方案中,只有少數目標方案從異步IO中獲得任何益處。但是,這些好處可能是巨大的,但僅限於這些情況。

下面是另一個有用的帖子:https://stackoverflow.com/a/25087273/122718

2

如果它是可以在後臺運行,並沒有強制同步的東西,嘗試在Task.Run包裝的代碼(即調用異步方法)( )。我不確定這會解決「死鎖」問題(如果它不同步,那是另一個問題),但是如果您想從異步/等待中受益,如果您一直沒有異步,除非您在後臺線程中運行,否則我不確定是否有好處。我有一個例子,在幾個地方添加Task.Run()(在我的情況下,從我更改爲異步的MVC控制器)並調用異步方法不僅提高了性能,而且提高了可靠性(不確定它是一個「僵局」,但似乎是類似的東西)在較重的負載下。

你會發現使用Task.Run()被一些人認爲是一種不好的方式來做到這一點,但我真的不能在我的情況下看到更好的方式來做到這一點,它確實似乎是一種提升。也許這是其中一種理想的方式來做它,而不是讓它在你所處的不完美的情況下工作的方式之一。:-)

[由於請求代碼更新]

因此,正如其他人發佈的那樣,您應該「完全異步」。就我而言,我的數據不是異步的,但我的用戶界面是。所以,我儘可能地減少了異步,然後用Task.Run包裝我的數據調用,這樣纔有意義。我認爲,這是一個竅門,要弄清楚事情是否可以並行運行,否則,你只是同步的(如果你使用異步並立即解決它,迫使它等待答案)。我有一些可以並行執行的讀取操作。

在上面的示例中,我認爲只要有意義,就必須先進行異步處理,然後在某個時刻確定可以分離出哪些地方並獨立於其他代碼執行操作。假設您有一個可以保存數據的操作,但您並不需要等待響應 - 您可以保存並完成操作。唯一需要注意的是不要等待該線程/任務完成才能關閉程序。代碼的意義在哪裏取決於您。

語法很簡單。我使用了現有的代碼,將控制器更改爲異步,返回之前返回的我的類的任務。

var myTask = Task.Run(() => 
{ 
    //...some code that can run independently.... In my case, loading data 
}); 
// ...other code that can run at the same time as the above.... 

await Task.WhenAll(myTask, otherTask); 
//..or... 
await myTask; 

//At this point, the result is available from the task 
myDataValue = myTask.Result; 

查看MSDN瞭解可能是更好的例子: https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx

[更新2,更相關的原題]

比方說,你的數據讀取是異步方法。

private async Task<MyClass> Read() 

你可以調用它,保存任務,並等待它時準備:

var runTask = Read(); 
//... do other code that can run in parallel 

await runTask; 

所以,爲了這個目的,調用異步代碼,這是原來的海報正在請求,我不要以爲你需要Task.Run(),儘管我不認爲你可以使用「await」,除非你是一個異步方法 - 你需要一個替代的Wait語法。

訣竅是,如果沒有一些代碼可以並行運行,就沒什麼意義了,所以考慮多線程仍然是關鍵。

+0

給我看一些代碼, –

+0

你可以給我一個如何在我的代碼上使用Task.Run的例子嗎?謝謝 – Riccardo

+0

我添加了更多信息來涵蓋兩種情況。對不起,我不得不通過他們完成理解。 –