2013-03-19 163 views
56

如果您在體系結構的較低級別使用async/await,是否需要將異步/等待調用「泡沫化」到底,是否無效,因爲您基本上正在創建每個圖層的新線程(異步調用每個圖層的異步函數,還是它並不重要,並且只取決於您的偏好? 。EFasync/await的體系結構

我的倉庫是這樣的:

public class EntityRepository<E> : IRepository<E> where E : class 
{ 
    public async virtual Task Save() 
    { 
     await context.SaveChangesAsync(); 
    } 
} 

現在我的業務層是這樣的:

public abstract class ApplicationBCBase<E> : IEntityBC<E> 
{ 
    public async virtual Task Save() 
    { 
     await repository.Save(); 
    } 
} 

然後當然我在我的UI方法將有調用時遵循相同的模式。

是這樣的:

  1. 必要
  2. 負性能
  3. 只是一個偏好的事

即使這不是在單獨的層中使用/項目,同樣的問題適用如果我正在調用同一類中的嵌套方法:

private async Task<string> Dosomething1() 
    { 
     //other stuff 
     ... 
     return await Dosomething2(); 
    } 
    private async Task<string> Dosomething2() 
    { 
     //other stuff 
     ... 
     return await Dosomething3(); 
    } 
    private async Task<string> Dosomething3() 
    { 
     //other stuff 
     ... 
     return await Task.Run(() => ""); 
    } 

回答

55

如果您正在使用異步/在你的體系結構中處於較低級別,是否需要將異步/等待調用「泡沫化」,效率不高,因爲基本上爲每個層創建一個新線程(異步調用每個層的異步函數,還是它沒有真正重要,只是取決於你的偏好?

這個問題表明有幾個誤區。

首先,您不要每次調用異步函數時都會創建一個新線程。

其次,你不需要聲明一個異步方法,只是因爲你正在調用一個異步函數。如果你感到快樂與已經返回的任務,剛剛返回從具有異步修飾符的方法:

public class EntityRepository<E> : IRepository<E> where E : class 
{ 
    public virtual Task Save() 
    { 
     return context.SaveChangesAsync(); 
    } 
} 

public abstract class ApplicationBCBase<E> : IEntityBC<E> 
{ 
    public virtual Task Save() 
    { 
     return repository.Save(); 
    } 
} 

稍微更有效,因爲它不」不涉及一個創建狀態機的原因 - 但更重要的是,它更簡單。

任何異步方法,其中有單個await表達等待TaskTask<T>,就在沒有進一步處理的方法結束時,會更好寫入而不使用異步/等待。所以這樣的:

public async Task<string> Foo() 
{ 
    var bar = new Bar(); 
    bar.Baz(); 
    return await bar.Quux(); 
} 

是更好的寫法如下:

public Task<string> Foo() 
{ 
    var bar = new Bar(); 
    bar.Baz(); 
    return bar.Quux(); 
} 

(理論上有正在創建的任務非常細微的差別,因此來電者可以添加延續,但在絕大多數的例如,您將不會注意到任何區別。)

+0

請注意,您也可能會標記一條消息,例如您描述爲「異步」的消息以添加錯誤處理。如果您等待任務,則可以將調用包裝在「try/catch」塊中,而不是將延續添加到所提供的任務中。 – Servy 2013-03-19 15:47:49

+0

@Servy:是的,但在這種情況下,它不符合提供額外處理的描述。在不改變API的情況下,你可以做到這一點很方便,因爲... – 2013-03-19 15:53:11

+0

所以,試着理解它,如果在同一個函數中出現的東西需要返回異步,我才真正需要使用async/await方法?如果它是火,忘了或最後的聲明那麼就沒有必要等待了? – valdetero 2013-03-19 16:43:06

27

效率不高,因爲您基本上是爲每個圖層創建一個新的線程(爲每個圖層異步調用一個異步函數,或者它並不重要,只取決於您的偏好?

不。異步方法不一定使用新線程。在這種情況下,由於底層異步方法調用是IO綁定方法,因此實際上不應創建新線程。

是這樣的:

1. necessary 

是必要的「泡沫」,如果你想保持異步操作的異步調用。然而,這確實是首選,因爲它可以充分利用異步方法,包括將它們組合在整個堆棧中。

2. negative on performance 

不會。如上所述,這不會產生新的線程。有一些開銷,但其中大部分可以最小化(見下文)。

3. just a matter of preference 

不是如果你想保持這種異步。你需要做到這一點,以保持跨棧的異步。

現在,有些事情可以做,以提高性能。這裏。如果你只是包裝異步方法,您不需要使用的語言功能 - 只返回Task

public virtual Task Save() 
{ 
    return repository.Save(); 
} 

repository.Save()方法已返回一個Task - 你不只是需要等待它將其包裝回Task。這將使該方法更有效。

你也可以有你的「低層次」異步方法使用ConfigureAwait,以防止他們需要調用同步上下文:

private async Task<string> Dosomething2() 
{ 
    //other stuff 
    ... 
    return await Dosomething3().ConfigureAwait(false); 
} 

這大大降低了參與各await如果你不需要的開銷擔心調用上下文。在處理「庫」代碼時,這通常是最好的選擇,因爲「外部」將捕獲UI的上下文。庫的「內部」運作通常不關心同步上下文,因此最好不要捕獲同步上下文。

最後,我想告誡你的例子之一:

private async Task<string> Dosomething3() 
{ 
    //other stuff 
    ... 
    // Potentially a bad idea! 
    return await Task.Run(() => ""); 
} 

如果你正在做這,在內部,使用Task.Run以「創造異步」周圍的東西,這不是本身異步異步方法,您正在有效地將同步代碼封裝到異步方法中。這使用ThreadPool線程,但可以「隱藏」它這樣做的事實,有效地使API誤導。通常情況下,將呼叫保留爲Task.Run以進行最高級別的調用,並讓底層方法保持同步,除非它們真正能夠利用異步IO或除Task.Run之外的某種卸載方式。 (這不是總是真實的,但「異步」的代碼通過Task.Run裹滿同步代碼,然後通過異步返回/等待往往是一個設計缺陷的徵兆。)

+0

最後一個方法中的Task.Run()僅僅是一個用於示例目的的人爲示例。在這種情況下,它可以是任何返回「Task」或「Task 」的方法。我只是想展示方法的嵌套。 – valdetero 2013-03-19 15:49:37

+2

@valdetero是的,但具體的例子是有效地使用異步/等待人們不知道的反模式。我想我會指出,儘管我也提到過這不一定是錯誤的,只是謹慎使用。 – 2013-03-19 15:50:50

+0

return Task.CompletedTask; – 2017-03-11 17:28:26