2015-01-06 49 views
3

我是C#中的異步編程新手,我仍然對一些事情感到困惑。 我已經讀過.NET 4.5之後,APM和EAP不再推薦用於新開發,因爲TAP應該替代它們(source)。是否可以等待未聲明爲異步的IO操作?如果不是,我該怎麼辦?

我想我理解async/await是如何工作的,我可以使用它們來執行具有異步方法的IO操作。例如,我可以編寫一個異步方法,等待HttpWebClient的GetStringAsync結果,因爲它被聲明爲異步方法。那很棒。

我的問題是:如果我們有一個IO操作發生在未聲明爲異步的方法中,該怎麼辦?是這樣的:假設我有一個有一個方法

string GetResultFromWeb() 

哪些查詢從Web東西的API。而且我有很多不同的查詢要做,而我必須使用這樣做。然後我需要處理每個查詢結果。我明白我應該這樣做,如果這是一個異步方法:

Task<string> getResultTask = GetResultFromWeb(myUrl); 
// Do whatever I need to do that doesn't need the query result 
string result = await getResultTask; 
Process(result); 

但因爲它不是,我不能等待它 - 它告訴我的字符串不是awaitable。所以我的問題是:是否有任何方式異步執行這些IO操作,而不必爲每個查詢創建一個線程?如果可以的話,我想創建儘可能少的線程,而不必阻塞任何線程。

我發現這樣做的一種方法是通過執行APM,跟隨Jeffrey Richter的this article,然後在我的Begin方法中調用ThreadPool.QueueWorkItem(GetResultFromWeb,asyncResult)。就像這樣:

public class A { 
    private void DoQuery(Object ar){ 
     AsyncResult<string> asyncResult = (AsyncResult<string>) ar; 
     string result = GetResultFromWeb(); 
     asyncResult.SetAsCompleted(result, false); 
    } 

    public IAsyncResult BeginQuery(AsyncCallback){ 
     AsyncResult<string> asyncResult = new AsyncResult<string>(callback, this); 
     ThreadPool.QueueUserWorkItem(DoQuery, asyncResult); 
     return asyncResult; 
    } 

    public string EndQuery(IAsyncResult ar){ 
     AsyncResult<string> asyncResult = (AsyncResult<string>)ar; 
     return asyncResult.EndInvoke(); 
    } 
} 

然後我使用AsyncEnumerator並開始(BeginQuery)幾個查詢並完成(使用收益回報/ EndQuery)處理結果作爲其中的每一個。這似乎運作良好。但是,在閱讀了太多以至於APM已經過時之後,我想知道如何使用TAP來做到這一點。另外,這種APM方法有什麼問題嗎?

謝謝!

回答

2

如果我們有一個IO操作發生在未聲明爲異步的方法中,該怎麼辦?

在這種情況下,I/O操作被阻止。換句話說,GetResultFromWeb會阻止調用線程。記住,當我們通過其餘的...

我必須使用這種方法來做到這一點。

通過這個我推斷你不能寫一個GetResultFromWebAsync異步的方法。因此,任何線程在做web請求必須被阻止。

是否有任何方式異步執行這些IO操作,而不必爲每個查詢創建一個線程?

最自然的方法是編寫一個GetResultFromWebAsync方法。因爲這是不可能的,所以你的選擇是:阻塞調用線程,或阻塞其他線程(即線程池線程)。阻塞線程池線程是一種我稱之爲「假異步」的技術 - 因爲它看起來是異步的(即不阻塞UI線程),但它確實不是(即它只是阻塞了線程池線程)。

如果可以,我想創建儘可能少的線程,而不必阻塞任何線程。

考慮到約束條件,這是不可能的。如果您的必須使用GetResultFromWeb方法,並且該方法的調用線程,則線程必須被阻止。

的一種方式,我發現這樣做是通過實現APM,本文從傑弗裏裏希特以下,然後,在我的Begin方法,我稱之爲ThreadPool.QueueWorkItem(GetResultFromWeb,asyncResult)。

在這種情況下,您的代碼正在公開一個異步API(begin/end),但在實現中它只是在線程池線程上調用GetResultFromWeb。即,這是假的異步。

這似乎工作得很好。

它的工作原理,但它不是真正的異步。

但是,在閱讀了太多的APM後,我想知道如何使用TAP來做到這一點。

正如其他人已經注意到的,有一種更簡單的方式來安排工作到線程池:Task.Run

真正的異步是不可能的,因爲你有一個阻止方法,你必須使用。所以,你所能做的只是一個解決方法 - 假異步,a.k.a.阻塞線程池線程。要做到這一點最簡單的方法是:

Task<string> getResultTask = Task.Run(() => GetResultFromWeb(myUrl)); 
// Do whatever I need to do that doesn't need the query result 
string result = await getResultTask; 
Process(result); 

(比APM和AsyncEnumerator更清潔的代碼)

請注意,我不創建一個GetResultFromWebAsync方法是使用假異步執行建議。任務返回,異步後綴方法應該遵循Task-based Asynchronous Pattern guidelines,這意味着真實異步。

換句話說,正如我在我的博客中更詳細地描述的那樣,use Task.Run to invoke a method, not to implement a method

+0

很好的回答!謝謝! –

1

一個簡單的方法來做你正在尋找的是使用Task類調​​用該方法。在你的情況下,它會是這個樣子:

Task<string> getResultTask = Task.Run<string>(()=>GetResultFromWeb(myUrl)); 
// Do whatever I need to do that doesn't need the query result 
string result = await getResultTask; 
Process(result); 

雖然作爲IAsyncResult選項執行此操作將創建另一個線程,這大大簡化了過程。

+2

不錯的答案,不過有一個'async'方法,只是委託給'Task.Run'不推薦使用,因爲'async'方法通常不會啓動一個新的線程。 –

+0

不錯,但不是這爲每次調用GetResultFromWebAsync創建一個新線程?我認爲APM會更有效率,因爲它不會爲每次調用創建不同的線程,而是爲這些任務排隊任務併爲其分配更少量的線程,從而消除創建新線程的開銷。我錯了嗎? –

+0

@DerekPatton如果我記得我讀的是正確的,我相信'Task'類在內部使用了'ThreadPool'。 – JRLambert

4

您的API使用較早的開始/結束模型進行異步。這通過

Task.Factory.FromAsync<string>(BeginQuery, EndQuery) 

適合TPL它返回一個Task<string>你可以await

+0

除了使代碼更簡單,是否有任何優勢(性能)做到這一點,而不是僅僅堅持舊的Begin/End方法? –

+0

@DerekPatton:與其他異步調用交錯很容易。 –

+1

@DerekPatton:另外,我不知道爲什麼你認爲可能會有性能優勢。包裝永遠不會比它所依賴的基礎更快。 –

相關問題