2017-07-24 36 views
15

我有一種情況,我有一個由特殊工廠創建的對象樹。這有點類似於DI容器,但不完全。強制C#異步任務是懶惰?

創建對象總是通過構造函數發生,而對象是不可變的。

對象樹的某些部分在給定的執行過程中可能不需要,應該延遲創建。所以構造參數應該是按需創建的工廠。這看起來像Lazy的工作。

但是,對象創建可能需要訪問緩慢的資源,因此始終是異步的。 (對象工廠的創建函數返回Task。)這意味着Lazy的創建功能需要是異步的,因此注入類型需要爲Lazy<Task<Foo>>

但我寧願沒有雙重包裝。我想知道是否有可能迫使Task懶惰,即創建一個Task,保證在等待之前不會執行。據我所知,Task.RunTask.Factory.StartNew可能在任何時候開始執行(例如,如果一個線程從池中是空閒的),即使沒有任何東西在等待它。

public class SomePart 
{ 
    // Factory should create OtherPart immediately, but SlowPart 
    // creation should not run until and unless someone actually 
    // awaits the task. 
    public SomePart(OtherPart eagerPart, Task<SlowPart> lazyPart) 
    { 
    EagerPart = eagerPart; 
    LazyPart = lazyPart; 
    } 

    public OtherPart EagerPart {get;} 
    public Task<SlowPart> LazyPart {get;} 
} 
+3

這有用嗎? https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/ – DavidG

+0

@DavidG謝謝,我以前閱讀過這篇文章。如果我不能做一個普通的'Task'懶惰,這將是我將使用的解決方法,但我仍然希望有一種方法來做到這一點。 –

+2

如果它尚未啓動,Await將不會爲您啓動任務,因此您無法「創建一個保證不會執行的任務,直到等待完成」(除非您在等待之前自行啓動)。 – Evk

回答

17

我不知道到底爲什麼要避免使用Lazy<Task<>>,,但如果只是爲了保持API更容易使用,因爲這是一個屬性,你可以用支持字段做到這一點:

public class SomePart 
{ 
    private readonly Lazy<Task<SlowPart>> _lazyPart; 

    public SomePart(OtherPart eagerPart, Func<Task<SlowPart>> lazyPartFactory) 
    { 
     _lazyPart = new Lazy<Task<SlowPart>>(lazyPartFactory); 
     EagerPart = eagerPart; 
    } 

    OtherPart EagerPart { get; } 
    Task<SlowPart> LazyPart => _lazyPart.Value; 
} 

這樣,使用就好像它只是一個任務,但初始化是懶惰的,只會在需要時纔會產生工作。

0

使用爲Task構造使得延遲又名不運行的任務,直到你說它運行,所以你可以做這樣的事情:

public class TestLazyTask 
{ 
    private Task<int> lazyPart; 

    public TestLazyTask(Task<int> lazyPart) 
    { 
     this.lazyPart = lazyPart; 
    } 

    public Task<int> LazyPart 
    { 
     get 
     { 
      // You have to start it manually at some point, this is the naive way to do it 
      this.lazyPart.Start(); 
      return this.lazyPart; 
     } 
    } 
} 


public static async void Test() 
{ 
    Trace.TraceInformation("Creating task"); 
    var lazyTask = new Task<int>(() => 
    { 
     Trace.TraceInformation("Task run"); 
     return 0; 
    }); 
    var taskWrapper = new TestLazyTask(lazyTask); 
    Trace.TraceInformation("Calling await on task"); 
    await taskWrapper.LazyPart; 
} 

結果:

SandBox.exe Information: 0 : Creating task 
SandBox.exe Information: 0 : Calling await on task 
SandBox.exe Information: 0 : Task run 

然而,我強烈建議您使用Rx.NETIObservable,因爲在您的情況下,您將減少處理少的麻煩天真的情況下,在適當的時候開始你的任務。 此外,它使得代碼有點清潔在我看來

public class TestLazyObservable 
{ 
    public TestLazyObservable(IObservable<int> lazyPart) 
    { 
     this.LazyPart = lazyPart; 
    } 

    public IObservable<int> LazyPart { get; } 
} 


public static async void TestObservable() 
{ 
    Trace.TraceInformation("Creating observable"); 
    // From async to demonstrate the Task compatibility of observables 
    var lazyTask = Observable.FromAsync(() => Task.Run(() => 
    { 
     Trace.TraceInformation("Observable run"); 
     return 0; 
    })); 

    var taskWrapper = new TestLazyObservable(lazyTask); 
    Trace.TraceInformation("Calling await on observable"); 
    await taskWrapper.LazyPart; 
} 

結果:

SandBox.exe Information: 0 : Creating observable 
SandBox.exe Information: 0 : Calling await on observable 
SandBox.exe Information: 0 : Observable run 

更清楚:這裏的Observable處理時開始任務,這是默認情況懶惰和意志每次訂購時都會運行該任務(這裏的訂閱由使用await關鍵字的服務器使用)。

如果需要,您可以每分鐘(或曾經)只運行一次任務,並在所有用戶中發佈結果以節省性能,例如在真實世界的應用程序中,所有這些和許多更多的是由觀察者處理的。

+1

'我強烈建議你使用Rx.NET和IObservable''' - 這就是我稱之爲這個特殊問題的矯枉過正。 Reactiveness很酷,但我會保持一段時間 – pkuderov

+0

如果你不忽略所有在日常應用中必須處理的特定問題的數量,那麼這並不過分。你可以爭辯說這就像是一個很好的「使用jquery」的東西,它是某種方式,但我真的覺得OP的場景與此有關。另外我沒有Observables回答他的問題,我提到它應該使用的東西。 – Uwy

+0

這就是爲什麼最好不要通過選擇不適當的工具來創建更多問題和提高熵,imho – pkuderov

2

@Max的回答是不錯,但我想補充這是建立在斯蒂芬Toub頂部的版本「在註釋中提到的文章:

public class SomePart: Lazy<Task<SlowPart>> 
{ 
    public SomePart(OtherPart eagerPart, Func<Task<SlowPart>> lazyPartFactory) 
     : base(() => Task.Run(lazyPartFactory)) 
    { 
     EagerPart = eagerPart; 
    } 

    public OtherPart EagerPart { get; } 
    public TaskAwaiter<SlowPart> GetAwaiter() => Value.GetAwaiter(); 
} 
  1. SomePart的從Lazy<Task<>>明確繼承所以它的明確它是懶惰異步

  2. 調用基構造函數將lazyPartFactory改爲Task.Run以避免長時間阻塞,如果該工廠在真正的異步部分之前需要一些cpu繁重的工作。如果不是您的情況,只需將其更改爲base(lazyPartFactory)

  3. SlowPart可通過TaskAwaiter訪問。所以SomePart的公共接口:

    • var eagerValue = somePart.EagerPart;
    • var slowValue = await somePart;
+0

也許不是更好@Max答案,因爲使用組合而不是繼承? IsValueCreated可能會在API上下文中被錯誤解釋,不是嗎? –

+0

是的,完全同意 – pkuderov