2016-10-28 50 views
2

昨天我問了一個question,答案很好。但是現在我想了解await的作用以及任務執行的工作方式。有和沒有等待操作員的任務執行

我讀過關於await:await運算符應用於異步方法中的任務,以掛起方法的執行,直到等待完成的任務完成。該任務代表正在進行的工作(來自msdn網站)。

Task.run:Run方法允許您在單個方法調用中創建和執行任務,並且是對StartNew方法(來自msdn站點)的簡單替代方案。

現在,隨着代碼:

 public async Task YourFunc() 
     { 
      Exception error = null; 
      try 
      { 
       var task = Task.Run(() => 
         { 
          Thread.Sleep(3000); 
          throw new ArgumentException("test argument exception"); 
         }); 
       var completed = task.IsCompleted; 
       var faulted = task.IsFaulted; 
       Console.WriteLine(completed); 
       Console.WriteLine(faulted); 
      } 
      catch (Exception ex) 
      { 
       error = ex; 
      } 
      this.MigrationProcessCompleted(error); 
     } 

我已經刪除了的await操作,我已經設置斷點上線Console.WriteLine(completed);。爲什麼即使在這個斷點等待2-3分鐘後,任務也沒有完成,沒有出錯?我給自己定任務的代碼和內部斷點例外是trown,所以任務必須被標記爲故障或至少完成...

+1

你在2-3分鐘後檢查什麼?'task.IsFaulted'或簡單的'faulted'變量在3分鐘前對這個值進行採樣? –

+0

task.IsFaulted –

+1

沒有等待,Task完成比YourFunc退出的時間晚了很多,所以嘗試抓取並且實際上整個方法完全沒用。完成和錯誤都是錯誤的,因爲在你點擊它們的那一刻,任務還沒有完成或者出錯。至於斷點 - 所有線程在斷點處暫停,所以無論等待多長時間 - 任務仍在進行中。 – Evk

回答

1

沒有人真的回答了你的問題。如果您等待超過3秒鐘,您有合理的期望看到task.isCompletedtask.isFaulted爲真。

您的期望沒有錯。這裏的問題只是調試器的工作方式。當你停在那個等待事件的斷點處時,所有其他線程也停止,所以沒有任何進展,並且異常還沒有被拋出。

這僅僅是一個調試的怪癖,你有幾種選擇,如果你想看到你所期望的結果:

  1. 凍結而不是使用斷點的線程。爲了做到這一點,在var task=Task.Run()之後放置一個斷點,然後在調試器中打開「線程」窗口。找到當前線程(帶有黃色箭頭),右鍵單擊它並選擇「凍結」,然後按F8或單擊繼續讓應用程序繼續運行。 3-4秒後,單擊調試器上的「暫停」按鈕並再次雙擊凍結的線程。您現在可以檢查task.IsCompletedtask.IsFaulted的值,它們應該是正確的。
  2. 添加超時您檢查任務之前:

代碼:

var task = Task.Run(() =>{ 
      Thread.Sleep(3000); 
      throw new ArgumentException("test argument exception"); 
     }); 
     Thread.Sleep(5000); 
     var completed = task.IsCompleted; 
     var faulted = task.IsFaulted; 
     Console.WriteLine("Completed ::" + completed); 
     Console.WriteLine("Faulted ::" + faulted); 

現在你可以把一個斷點Thread.Sleep(5000)後,確認任務發生故障/完成。

請記住,正如其他人所說,立即使用awaitedTask.Run()幾乎總是一個錯誤。請記住,async/await的全部內容是釋放當前線程,所以它不會做無用的等待。如果你做了Task.Run然後await你沒有取得任何成就,因爲你只是釋放當前線程(把它放回線程池),但Task.Run()將佔用線程池中的一個線程。從概念上講,你所做的一切都是將你的工作形式從一個線程標識移動到另一個線程,而沒有真正的收益(即使你的工作在CPU上)。

在這種情況下,您最好不要在做Task.Run而只是在當前線程上同步執行工作。

+0

對於實際的應用程序,除了等待任務以外,沒有別的辦法來確保調用者可以接收結果/例外。 正如我所提到的,當理想的異步除了調用線程之外沒有其他線程時,由於調用線程池線程,這不是真正的異步方式。建議不要'等待Task.Run()'不正確,請建議可以在實際應用中使用異步方法的替代方法,我相信'Thread.Sleep'只是爲了調試應用程序。 –

+0

必須說,與調試器凍結線程相關的問題是很好和正確的,我錯過了,因爲整個焦點是使用「等待」正確設計'Async'方法。 –

+0

他的問題沒有聽到這是任何真正的應用。這只是一次性代碼,他只是想了解它是如何工作的。他的問題是關於他爲什麼在調試時沒有看到他期望看到的內容。所以我專注於回答這個問題。 – dtabuenc

0

退房一些修改你的代碼下面:

幾個重要要點:

  • 當前實現是不是真正Async,因爲它創造了一個同步邏輯的包裝,調用線程池中的線程,在現實中there's no thread (Stepehen Cleary)
  • 守則並有助於釋放調用線程,但與真正異步操作不同,它是計算綁定操作,它需要一個單獨的線程而不是真正的IO操作,它在IO完成端口(基於硬件的隊列)上執行
  • 正如您可能已經理解的,因爲主機進程退出時沒有執行await,因爲主機進程退出,所以無法獲取異常信息,因爲任務在線程池線程上執行,後者在後臺,前臺沒有任何東西可以接收它
  • await does help在解包和捕獲任務異常,但主機進程仍然是重要的知道細節
  • ConfigureAwait(false).GetAwaiter().GetResult()僅用於啓用控制檯程序(作爲主機)Catch t他例外,否則這不是一個建議的模式,並且對於像ASP.net MVC這樣的標準異步調用堆棧,簡單的await會在調用堆棧頂部做需求
  • 另外注意ConfigureAwait(false)對於Console,它們沒有什麼UI /同步環境,否則就不是必需的,直到除非啓用完整的調用鏈忽略上下文貫穿
  • 如果你正在尋找的異步方式的情況下完成,然後檢查了Task Completion Source
  • 另請檢查Why Task StartNew is Dangerous

    class Program 
        { 
        static void Main(string[] args) 
        { 
         try 
         { 
          Program p = new Program(); 
          p.YourFunc().ConfigureAwait(false).GetAwaiter().GetResult(); 
         } 
         catch (Exception) 
         { 
          Console.WriteLine("Outer Exception"); 
         } 
        } 
    
        public async Task YourFunc() 
        { 
         Exception error = null; 
         try 
         { 
          var task = Task.Run(() => 
          { 
           Thread.Sleep(3000); 
           throw new ArgumentException("test argument exception"); 
          }); 
    
          await task; 
    
          var completed = task.IsCompleted; 
          var faulted = task.IsFaulted; 
          Console.WriteLine("Completed ::" + completed); 
          Console.WriteLine("Faulted ::" + faulted); 
         } 
         catch (Exception ex) 
         { 
          Console.WriteLine("Inner Exception"); 
          throw ex; 
         } 
         // Commented (No Implementation) 
         //this.MigrationProcessCompleted(error); 
        } 
        } 
    
+0

你對'ConfigureAwait(false)'的建議是倒退的。在控制檯應用程序中,根本不需要'ConfiugreAwait(false)',因爲沒有可能導致性能問題或死鎖的UI線程或ASP.net同步上下文。 'ConfigureAwait(false)'對UI應用程序和可能的ASP.NET應用程序至關重要,因爲它們使用同步上下文來限制可以使用的線程(導致性能問題或死鎖)。 – dtabuenc

+0

@dtabuenc,請閱讀完整的一系列詳細信息,我自己建議,這不是一個理想的用法,不能用於生產,它只用於調試應用程序,以便可以顯示Inner和Outer異常給用戶,因爲調用線程仍然可以接收異常。我可以通過使用'GetAwaiter()。GetResult()'來管理,但'ConfigureAwait(false)'確保我們完全忽略了上下文(如果有的話)。 –

+0

@dtabuenc,同樣對於任何Async方法來說,解開異常的理想方式總是「await」,當不使用時會導致OP所建議的情況,背景中的異常從未被捕獲,因爲沒有人可以接收它。 –

相關問題