2011-02-27 27 views
1

我正在閱讀Eric Lippert關於C#5新異步功能的博客article series。在那裏,他使用了一個方法的示例,從遠程位置獲取文檔,並且一旦檢索到,就將其歸檔到存儲驅動器中。這是他使用的代碼:C#5.0中的異步:Eric Lippert的示例如何工作?

async Task<long> ArchiveDocumentsAsync(List<Url> urls) 
{ 
    long count = 0; 
    Task archive = null; 
    for(int i = 0; i < urls.Count; ++i) 
    { 
    var document = await FetchAsync(urls[i]); 
    count += document.Length; 
    if (archive != null) 
     await archive; 
    archive = ArchiveAsync(document); 
    } 
    return count; 
} 

現在設想獲取文檔非常快。所以第一個文件被提取。之後,它開始被存檔,而第二個文檔正在被抓取。現在想象第二個文檔已被提取,並且第一個文檔仍被歸檔。這段代碼是否會開始提取第三個文檔或等到第一個文檔被存檔?

正如埃裏克在其文章中說,這段代碼被編譯器轉換成這樣:

Task<long> ArchiveDocuments(List<Url> urls) 
{ 
    var taskBuilder = AsyncMethodBuilder<long>.Create(); 
    State state = State.Start; 
    TaskAwaiter<Document> fetchAwaiter = null; 
    TaskAwaiter archiveAwaiter = null; 
    int i; 
    long count = 0; 
    Task archive = null; 
    Document document; 
    Action archiveDocuments =() => 
    { 
    switch(state) 
    { 
     case State.Start:  goto Start; 
     case State.AfterFetch: goto AfterFetch; 
     case State.AfterArchive: goto AfterArchive; 
    } 
    Start: 
    for(i = 0; i < urls.Count; ++i) 
    { 
     fetchAwaiter = FetchAsync(urls[i]).GetAwaiter(); 
     state = State.AfterFetch; 
     if (fetchAwaiter.BeginAwait(archiveDocuments)) 
     return; 
     AfterFetch: 
     document = fetchAwaiter.EndAwait(); 
     count += document.Length; 
     if (archive != null) 
     { 
     archiveAwaiter = archive.GetAwaiter(); 
     state = State.AfterArchive; 
     //----> interesting part! <----- 
     if (archiveAwaiter.BeginAwait(archiveDocuments)) 
      return; //Returns if archive is still working => Fetching of next document not done 
     AfterArchive: 
     archiveAwaiter.EndAwait(); 
     } 
     archive = ArchiveAsync(document); 
    } 
    taskBuilder.SetResult(count); 
    return; 
    }; 
    archiveDocuments(); 
    return taskBuilder.Task; 
} 

其他問題:

如果停止執行,將有可能繼續讀取文件?如果是,如何?

回答

9

這段代碼是否會開始提取第三個文檔或等到第一個文檔被存檔?

它等待。本文的重點在於描述控制流程如何與轉換協同工作,而不是實際描述管理讀取 - 歸檔操作的最佳系統。(*)您可以創建一個新的異步方法「FetchAndArchive」,它可以異步獲取一個文檔,然後將其存檔它是異步的。然後,您可以從另一個異步方法調用該方法一百次,其中每個任務都異步獲取文檔並將其歸檔。 方法的結果是一個組合的任務,它表示做這100個任務的工作,其中每個任務代表兩個任務的工作。

在這種情況下,只要其中一個提取操作無法立即生成其結果,就可以運行準備完成其歸檔步驟的其中一個任務。

我不想在本文中進入任務組合器;我想專注於更簡單的控制流程。


(*),你可能會關心他們發生了什麼樣的順序中,如果不是「下載文件和存檔」的操作是「獲取下一個視頻在這個系列並播放」。即使它們能夠更有效地到達亂序,您也不想將它們亂序播放。相反,你想在當前播放的時候下載下一個。

2

這段代碼讓它等到之前的文檔被存檔後纔開始存檔下一個。而且它只會開始下載第三個,一旦它開始歸檔第二個。

if (archive != null) 
     await archive; 

但我通常認爲抓取是緩慢的,因爲它從互聯網上下載,而歸檔是快速的,因爲它是到本地硬盤。但是,這當然取決於你的確切用例。

+0

好吧,讓我們假設你從本地驅動器獲取文件並將它們上傳到遠程位置...... – Simon

0

不使用異步/ AWAIT,同*僞代碼的功能將類似於

long ArchiveDocumentsAsync(List<Url> urls) 
    { 
    long count = 0; 
    Task archive = null; 

    for(int i = 0; i < urls.Count; ++i) 
    { 
     Task<Something> documentTask = FetchAsync(urls[i]); 

     //Wait for the completion of the task. 
     documentTask.Wait(); 

     //Get the results. 
     Something document = documentTask.getReturnValue(); 

     count += document.Length; 

     if (archive != null) { 
     //Wait for the completion of the task. 
     archive.Wait(); 
     } 

     archive = ArchiveAsync(document); 
    } 
    return count; 
    } 

請注意,我們永遠不會有兩次存取或同時有兩個Archivings。在第一次存檔完成之前,第二次存檔無法啓動,並且在第二次存檔啓動之前無法啓動第三次提取。

(*)現在的異步法寶:

編譯器生成的代碼,以便於Wait()來電實際上並不阻止當前線程的執行。函數ArchiveDocumentsAsync只是簡單地向其調用者「屈服」(除非調用者的結果是await),在這種情況下,該流程被髮送給調用者,等等。
Wait ed任務完成之後,編譯器生成的機器確保在下一個執行繼續執行。

備註: Eric Lippert已經回答了這個問題。我只想給我兩分錢,寫下我的理解,這樣你們就可以在這裏發出警告,如果這是錯誤的。