c# - how - wpf async await




異步編程和多線程有什麼區別? (2)

您的誤解非常普遍。 許多人被教導多線程和異步是同一回事,但事實並非如此。

類比通常會有所幫助。 您在餐廳做飯。 訂購雞蛋和吐司。

  • 同步:先煮雞蛋,再煮吐司。
  • 異步單線程:開始煮雞蛋並設置計時器。 您開始烤麵包,並設置一個計時器。 他們倆都在做飯時,您要打掃廚房。 計時器關閉後,您將雞蛋和乾麵包從烤麵包機中取出並送達。
  • 異步,多線程:您再僱用兩名廚師,一名廚師煮雞蛋,一名廚師烤麵包。 現在您需要協調廚師,以便他們在共享資源時不會在廚房互相衝突。 而且你必須付錢。

現在,多線程僅僅是異步的一種有意義嗎? 線程是關於工人的。 異步是關於任務的 。 在多線程工作流中,您將任務分配給工作人員。 在異步單線程工作流中,您可以看到一個任務圖,其中某些任務取決於其他任務的結果。 在每個任務完成時,給定剛完成的任務的結果,它將調用計劃下一個可以運行的任務的代碼。 但是您(希望)僅需要一名工作人員即可執行所有任務,而不需要每個任務一名工作人員。

這將有助於認識到許多任務不是處理器約束的。 對於與處理器相關的任務,合理的做法是僱用與處理器數量一樣多的工作程序(線程),為每個工作程序分配一個任務,為每個工作程序分配一個處理器,並且讓每個處理器除了計算結果外,別無其他工作盡快。 但是對於沒有在處理器上等待的任務,您根本不需要分配工作器。 您只需要等待消息到達就可以得到結果,然後 在等待時執行其他操作即可 。 當該消息到達時,您可以將已完成任務的繼續安排為待辦事項列表上的下一個要檢查的內容。

因此,讓我們更詳細地看一下喬恩的例子。 怎麼了?

  • 有人調用DisplayWebSiteLength。 誰? 我們不在乎。
  • 它設置一個標籤,創建一個客戶端,並要求客戶端獲取一些東西。 客戶端返回一個對象,該對象表示獲取某些內容的任務。 該任務正在進行中。
  • 它在另一個線程上進行嗎? 可能不是。 閱讀 blog.stephencleary.com/2013/11/there-is-no-thread.html ,了解為什麼沒有線程。
  • 現在我們等待任務。 怎麼了? 我們檢查任務在創建和等待之間是否完成。 如果是,那麼我們獲取結果並繼續運行。 讓我們假設它還沒有完成。 我們將該方法的其餘部分簽名為該任務的繼續並返回
  • 現在,控制權已返回給調用者。 它有什麼作用? 無論它想要什麼。
  • 現在,假設任務已完成。 它是怎麼做到的? 也許它正在另一個線程上運行,或者也許我們剛剛返回的調用方允許它在當前線程上運行完成。 無論如何,我們現在有一個完成的任務。
  • 完成的任務要求正確的線程(可能是 唯一的 線程)再次運行任務。
  • 控制權立即返回到我們剛剛在等待點離開的方法。 現在有一個可用的結果,因此我們可以分配 text 並運行該方法的其餘部分。

就像我的類比。 有人要求您提供文件。 您發送了該文檔的郵件,並繼續進行其他工作。 當它到達郵件中時,您會收到信號通知,並且當您感到喜歡時,就完成了其餘的工作流程-打開信封,支付郵寄費,無論如何。 您無需僱用其他工人即可為您完成所有這些工作。

我認為它們基本上是同一回事–編寫在處理器之間(在具有2個以上處理器的機器上)將任務分割的程序。 然後,我正在閱讀 https://msdn.microsoft.com/en-us/library/hh191443.aspx ,它說:

異步方法旨在作為非阻塞操作。 在等待的任務運行時,異步方法中的等待表達式不會阻塞當前線程。 取而代之的是,表達式將方法的其餘部分作為繼續進行簽名,並將控制權返回給異步方法的調用方。

async和await關鍵字不會導致創建其他線程。 異步方法不需要多線程,因為異步方法不會在自己的線程上運行。 該方法在當前同步上下文上運行,並且僅在該方法處於活動狀態時才在線程上使用時間。 您可以使用Task.Run將與CPU綁定的工作移至後台線程,但是後台線程對僅等待結果可用的進程沒有幫助。

我想知道是否有人可以幫我翻譯成英文。 似乎在異步性(是一個詞?)和線程之間進行了區分,這意味著您可以擁有一個具有異步任務但沒有多線程的程序。

現在,我了解了異步任務的想法,例如pg上的示例。 喬恩·斯基特(Jon Skeet)的 C#深度 467 ,第三版

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

async 關鍵字的意思是“ 該函數,無論何時被調用,在被調用後所有事情都需要完成的上下文中不會被調用。”

換句話說,在某些任務的中間編寫它

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

,因為 DisplayWebsiteLength()xy 無關,將導致 DisplayWebsiteLength() 在“後台”執行,例如

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

顯然,這是一個愚蠢的例子,但是我是正確的,還是我完全感到困惑?

(此外,我對於為什麼在上述函數的主體中從未使用 sendere 感到困惑。)


瀏覽器內Javascript是沒有線程的異步程序的一個很好的例子。

您不必擔心多個代碼會同時觸摸相同的對象:每個功能將在允許任何其他JavaScript在頁面上運行之前完成運行。

但是,當執行AJAX請求之類的操作時,根本沒有代碼在運行,因此其他javascript可以響應單擊事件,直到該請求返回並調用與之關聯的回調。 如果在AJAX請求返回時這些其他事件處理程序中的一個仍在運行,則直到它們完成後才會調用其處理程序。 即使有可能有效地暫停正在執行的操作,直到獲得所需的信息,也僅運行一個JavaScript“線程”。

在C#應用程序中,任何時候處理UI元素都會發生同樣的事情-僅在使用UI線程時才允許與UI元素進行交互。 如果用戶單擊了一個按鈕,而您想通過從磁盤讀取一個大文件來做出響應,那麼經驗不足的程序員可能會在點擊事件處理程序本身中讀取文件時出錯,這將導致應用程序“凍結”直到文件已完成加載,因為在釋放該線程之前,不允許它再響應任何單擊,懸停或任何其他與UI相關的事件。

程序員可能使用的一種避免該問題的方法是創建一個新線程來加載文件,然後告訴該線程的代碼,當文件加載時,它需要再次在UI線程上運行其餘代碼,以便可以更新UI元素。根據在文件中找到的內容。 直到最近,這種方法還是很流行,因為它使C#庫和語言變得容易,但是從根本上說,它比必須的複雜。

如果您考慮CPU在硬件/操作系統級別讀取文件時的操作,它基本上是在發出指令,將數據從磁盤讀取到內存中,並在出現以下情況時以“中斷”命中操作系統讀取完成。 換句話說,從磁盤(或實際上是任何I / O)讀取是一種固有的 異步 操作。 等待該I / O完成的線程的概念是庫開發人員為了簡化編程而創建的抽象概念。 這不是必需的。

現在,.NET中的大多數I / O操作都有一個您可以調用的對應的 ...Async() 方法,該方法幾乎立即返回 Task 。 您可以向此 Task 添加回調,以指定要在異步操作完成時運行的代碼。 您還可以指定要在其上運行該代碼的線程,還可以提供令牌,異步操作可以不時檢查該令牌,以查看是否決定取消異步任務,從而有機會迅速停止其工作。優雅地

在添加 async/await 關鍵字之前,C#在如何調用回調代碼方面更為明顯,因為這些回調採用與任務相關聯的委託的形式。 為了仍然為您提供使用 ...Async() 操作的好處,同時避免了代碼的複雜性, async/await 抽象了這些委託的創建。 但是它們仍然存在於編譯後的代碼中。

因此,您可以讓您的UI事件處理程序 await I / O操作,釋放UI線程以執行其他操作,並在完成讀取文件後或多或少地自動返回UI線程-創建一個新線程。





async-await