2014-02-21 156 views
3

我試圖讓我的頭在C#中的新異步功能,到目前爲止我注意到的最奇怪的事情是,異步功能的每個示例都有一個函數,等待另一個異步函數定義在框架中,但沒有一個具有自定義代碼。等待自定義函數

例如,我想是在文本文件中的每一行創建一個對象,而是以異步方式,使UI線程不會凍結:

async Task Read() 
{ 
    string[] subjectStrings = File.ReadAllLines(filePath); 
    for (int i = 0; i < subjectStrings.Length; i++) 
    { 
     Task<Subject> function = new Task<Subject>(code => new Subject((string)code), subjectStrings[i]); 
     try 
     { 
      Subject subject = await function; 
      subjects.Add(subject); 
     } 
     catch (Exception ex) 
     { 
      debugWriter.Write("Error in subject " + subjectStrings[i]); 
      continue; 
     } 
    } 
} 

正如你所看到的,我定義根據文本文件中的一行創建新對象Subject,然後等待此任務。如果我這樣做,調試器會跳到await一行,然後停止。就我所知,沒有更多的代碼運行。

如果我使用舊的異步功能,我只是使用Task.ContinueWith()並添加一個回調lambda,將主題添加到列表中,並在我的路上。

所以我的問題是:

  1. 爲什麼這個代碼不工作?你應該如何做一個自定義的異步方法,不使用任何異步方法本身?
  2. 你應該怎麼樣使用異步方法?你不能使用await,除非你在異步函數中,並且你不應該在沒有等待的情況下調用異步方法,那麼你如何首先從同步方法調用該方法?
+0

[你在說什麼關於此例將實施(http://stackoverflow.com/questions/5095183/how-will-i-run-an-async-taskt-method-synchronously)以及爲什麼你不應該那樣做...... –

+0

請不要在標題中加入languge標籤,沒有它就沒有意義。標籤用於此目的。 –

+0

但這隻與C#有關? – Miguel

回答

1

爲什麼這段代碼不起作用?你應該如何做一個自定義的異步方法,不使用任何異步方法本身?

使用await Task.Runawait Task.Factory.StartNew創建並運行任務。調用new Task將創建一個尚未啓動的任務。在大多數情況下,這是不必要的,但您可以通過此方式創建的任務調用Start

你應該如何使用異步方法?除非你在異步函數中,否則你不能使用await,並且你不應該在沒有等待的情況下調用異步方法,那麼你如何首先從同步方法調用該方法?

適當的 「根」 的異步調用取決於應用程序的類型:

  • 在控制檯應用程序:Wait上返回Task

  • 在GUI應用程序中:使用async void事件處理程序。

  • 在ASP.NET MVC中:控制器可以返回Task

+0

您可以詳細說明如何在控制檯應用程序中執行此操作嗎?等待返回的任務是什麼意思? – Miguel

+0

控制檯應用程序中的Main方法不能是異步的。所以如果你需要調用'async Task f()'方法,你可以把它叫做f()。Wait()'而不是'await f()'。這阻止了調用線程,直到調用完成。 – Athari

+0

好吧,所以這只是真的與GUI無關。 – Miguel

5

你不是開始的任務 - 所以它永遠不會結束。

使用Task.Run而不是new Task它會創建並開始任務。

注意,你還在同步讀取文件,這是不理想......如果你Subject構造真的需要那麼長時間才能完成,我會懷疑它是否應該是一個構造函數。

+0

+1爲異步構造函數:D – Luaan

+1

@Luaan最後一點是構造函數不應該做昂貴的操作,而不是他們應該異步執行它們。 –

+0

@StevenLiekens那裏沒有誤解。這就是我想強調的一點 - 如果你需要平行運行構造函數,那麼你正在做一些非常錯誤的事情。很多事情很可能。 – Luaan

0

你很困惑等待工作。等待使用async/await,工作沒有。這仍然意味着你可以await CPU密集型任務,但是你必須手動運行它,例如:

var result = await Task.Run(YourLongOperation); 

區分,可以幫助我理解這在腸道水平,即等待是合作 - 我自願放棄我的CPU時間份額,因爲我實際上並不需要它。另一方面工作必須並行運行。

在正常情況下,僅使用固有的異步程序,不必超過您開始使用的單個線程。將CPU綁定操作與I/O綁定操作組合在一起通常是一個壞主意(CPU綁定會阻塞,除非您明確地並行運行任務)。

1

怎麼是你應該做的是不使用任何異步方法本身就是一個自定義的異步方法?

你不知道。如果該方法沒有異步工作要做,它應該是同步的;它不應該是async

在覈心,所有的async方法歸結爲兩種方法之一。他們通過類似於Task.Run(不建議用於庫代碼)的方式將工作排隊到線程池,或者他們通過TaskCompletionSource<T>或諸如Task.Factory.FromAsync之類的快捷方式執行真正的異步工作。

你應該如何使用異步方法?除非你在異步函數中,否則你不能使用await,並且你不應該在沒有等待的情況下調用異步方法,那麼你如何首先從同步方法調用該方法?

你不知道。理想情況下,你應該一直是async。控制檯應用程序是此規則的一個例外;他們有一個同步Main。但是您應該一直使用WinForms,WPF,Silverlight,Windows應用商店,ASP.NET MVC,WebAPI,SignalR,iOS,Android和Windows Phone應用以及單元測試的異步方式。

您可以使用async方法通過await和組合器,如Task.WhenAllTask.WhenAny。這是使用async方法的最常見方式,但不是唯一的方法;例如,您可以調用async方法並將其作爲IObservable<T>使用。

+0

「如果該方法沒有異步工作要做,它應該是同步的」 我確實有異步工作。我想將這個文件讀入一個數組,*當*允許用戶在UI中做任何他們想要的。這應該如何同步? – Miguel

+0

如果每個方法都應該是異步的,爲什麼WPF或Winforms中不會自動實現異步功能? – Miguel

+0

@Miguel:如果你正在做文件I/O,那麼這是異步的,你應該使你的方法異步。我所做的一點是*不*每種方法都應該是異步的;它只應該是異步的,如果它有異步工作要做。 –

0

我不會再給你另一個技術解釋™,而是讓我根據你的代碼示例展示一個實際的例子。

從一個執行調用線程所有工作的同步版本開始。

class SubjectFactory 
{ 
    public IEnumerable<Subject> Read(string filePath) 
    { 
     string[] subjectStrings = File.ReadAllLines(filePath); 

     return Parse(subjectStrings); 
    } 

    private IEnumerable<Subject> Parse(IEnumerable<string> subjects) 
    { 
     string code = "XYZ"; 

     foreach (var subject in subjects) 
     { 
      yield return new Subject(code, subject); 
     } 
    } 
} 

假設在Subject構造輕巧,最大的瓶頸就是File.ReadAllLines。爲什麼?因爲磁盤I/O本質上很慢。

那麼你怎麼去把它包裝在一個任務中呢?

如果框架有一個File.ReadAllLinesAsync()方法,您可以等待並完成它。

public async Task<IEnumerable<Subject>> ReadAsync(string filePath) 
{ // Doesn't exist! 
    string[] subjectStrings = await File.ReadAllLinesAsync(filePath); 

    return this.Parse(subjectStrings); 
} 

不幸的是,生活是困難的,並行編程也是如此。看起來你必須重新發明輪子。

private async Task<string[]> ReadAllLinesAsync(string filePath) 
{ 
    ArrayList allLines = new ArrayList(); 

    using (var streamReader = new StreamReader(File.OpenRead(filePath))) 
    { 
     string line = await streamReader.ReadLineAsync(); 

     allLines.Add(line); 
    } 

    return (string[]) allLines.ToArray(typeof(string)); 
} 

現在你可以做同樣的事情,但使用自定義方法ReadAllLinesAsync()

public async Task<IEnumerable<Subject>> ReadAsync(string filePath) 
{ 
    // call with 'this' instead of 'File' 
    string[] subjectStrings = await this.ReadAllLinesAsync(filePath); 

    return Parse(subjectStrings); 
} 

與所有的地方,在您的WPF應用程序的所有你要做的是:

var filePath    = @"X:\subjects\"; 
var subjectFactory  = new SubjectFactory(); 
var subjectsCollection = await subjectFactory.ReadAsync(filePath); 
var observableCollection = new ObservableCollection<Subject>(subjectsCollection);