2016-04-01 40 views
15

我有以下方法:如何從非異步方法調用異步方法?

public string RetrieveHolidayDatesFromSource() { 
     var result = this.RetrieveHolidayDatesFromSourceAsync(); 
     /** Do stuff **/ 
     var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/ 
     return returnedResult; 
    } 


    private async Task<string> RetrieveHolidayDatesFromSourceAsync() { 
     using (var httpClient = new HttpClient()) { 
      var json = await httpClient.GetStringAsync(SourceURI); 
      return json; 
     } 
    } 

以上不工作,似乎無法正常返回任何結果。我不知道我在哪裏錯過了一個強制等待結果的聲明?我想RetrieveHolidayDatesFromSource()方法返回一個字符串。

下面的工作正常,但它是同步的,我相信它可以改進?請注意,以下是我想要更改爲異步的同步,但由於某種原因無法將頭部纏繞。

public string RetrieveHolidayDatesFromSource() { 
     var result = this.RetrieveHolidayDatesFromSourceAsync(); 
     /** Do Stuff **/ 

     var returnedResult = this.TransformResults(result); /** This is where Result is actually used**/ 
     return returnedResult; 
    } 


    private string RetrieveHolidayDatesFromSourceAsync() { 
     using (var httpClient = new HttpClient()) { 
      var json = httpClient.GetStringAsync(SourceURI); 
      return json.Result; 
     } 
    } 

我錯過了什麼嗎?

注意:由於某種原因,當我斷開上面的Async方法時,當它到達「var json = await httpClient.GetStringAsync(SourceURI)」行時,它剛剛離開斷點,我不能回到方法。

回答

23

我錯過了什麼嗎?

是的。異步代碼 - 就其性質而言,意味着當前線程在操作正在進行時未被使用。同步代碼 - 就其性質而言,意味着當前線程在操作過程中被阻塞。這就是爲什麼從字面上調用異步代碼的異步代碼甚至沒有意義。實際上,正如我在我的博客上描述的那樣,a naive approach (using Result/Wait) can easily result in deadlocks

首先要考慮的是:應該我的API是同步還是異步?如果它處理I/O(如本例中),則它爲should be asynchronous。因此,這將是一個更合適的設計:

public async Task<string> RetrieveHolidayDatesFromSourceAsync() { 
    var result = await this.DoRetrieveHolidayDatesFromSourceAsync(); 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result); /** Where result gets used **/ 
    return returnedResult; 
} 

正如我在async best practices article描述,你應該去「異步一路」。如果你不這樣做,你不會從異步中獲得任何好處,所以爲什麼要麻煩呢?

但是,讓我們說,你有興趣最終去異步,但是現在你不能改變一切,你只是想改變你的應用程序的一部分。這是一個非常普遍的情況。

在這種情況下,正確的做法是暴露兩個同步和異步API。最終,在所有其他代碼升級之後,可以刪除同步API。我在article on brownfield async development中探索了這種場景的各種選項;我個人最喜歡的是「布爾參數黑客」,這將是這樣的:

public string RetrieveHolidayDatesFromSource() { 
    return this.DoRetrieveHolidayDatesFromSourceAsync(sync: true).GetAwaiter().GetResult(); 
} 

public Task<string> RetrieveHolidayDatesFromSourceAsync() { 
    return this.DoRetrieveHolidayDatesFromSourceAsync(sync: false); 
} 

private async Task<string> DoRetrieveHolidayDatesFromSourceAsync(bool sync) { 
    var result = await this.GetHolidayDatesAsync(sync); 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result); 
    return returnedResult; 
} 

private async Task<string> GetHolidayDatesAsync(bool sync) { 
    using (var client = new WebClient()) { 
    return sync 
     ? client.DownloadString(SourceURI) 
     : await client.DownloadStringTaskAsync(SourceURI); 
    } 
} 

這種方法避免了重複代碼,也避免了與其他「同步過異步」反模式解決方案的常見任何死鎖或重入的問題。

請注意,我仍然將結果代碼視爲正確異步API路徑上的「中間步驟」。特別是,內部代碼必須回退WebClient(它支持同步和異步),而不是首選的HttpClient(它只支持異步)。一旦所有的調用代碼被更改爲使用RetrieveHolidayDatesFromSourceAsync而不是RetrieveHolidayDatesFromSource,那麼我會重新訪問並刪除所有技術債務,將其更改爲使用HttpClient並且僅爲異步。

+1

是'公共任務 RetrieveHolidayDatesFromSourceAsync()'缺少'await'? –

+3

@NickWeaver:不是,它不是'async',所以它不能使用'await'。 –

+0

我明白了。我想知道,因爲該方法的名稱最後說「異步」。爲什麼不設置爲異步? –

2
public string RetrieveHolidayDatesFromSource() { 
    var result = this.RetrieveHolidayDatesFromSourceAsync().Result; 
    /** Do stuff **/ 
    var returnedResult = this.TransformResults(result.Result); /** Where result gets used **/ 
    return returnedResult; 
} 

如果添加。結果的異步調用,它將執行並等待結果的到來,迫使它是同步

UPDATE:

private static string stringTest() 
{ 
    return getStringAsync().Result; 
} 

private static async Task<string> getStringAsync() 
{ 
    return await Task.FromResult<string>("Hello"); 
} 
static void Main(string[] args) 
{ 
    Console.WriteLine(stringTest()); 

} 

解決評論:這工作沒有任何問題。

+1

我不認爲這是工作,因爲我已經在方法中的「result.Result」。擁有this.RetrieveHolidayDatesFromSourceAsync()。結果會將結果更改爲一個非任務對象,這將導致「var returnedResult = this.TransformResults(result.Result)」中的錯誤。 –

+0

我更新了我的答案以顯示工作代碼。你可以看到getStringAsync是一個在同步方法(stringTest())中調用的異步方法,我得到所需的輸出沒有錯誤。你得到的錯誤是什麼? –

+4

「這工作沒有任何問題」:)...你可能會更好一點,並解釋如何處理在實際使用外部控制檯應用程序時產生的死鎖。 –