[英]Force C# async tasks to be lazy?
我有一種情況,我有一個由特殊工廠創建的對象樹。 這有點類似於 DI 容器,但又不完全相同。
對象的創建總是通過構造函數進行,並且對象是不可變的。
在給定的執行中可能不需要對象樹的某些部分,應該延遲創建。 所以構造函數參數應該只是一個按需創建的工廠。 這看起來像是Lazy
的工作。
但是,對象創建可能需要訪問緩慢的資源,因此始終是異步的。 (對象工廠的創建函數返回一個Task
。)這意味着Lazy
的創建函數需要是異步的,因此注入的類型需要是Lazy<Task<Foo>>
。
但我寧願沒有雙重包裝。 我想知道是否有可能強制一個Task
懶惰,即創建一個保證在等待它之前不會執行的Task
。 據我了解, Task.Run
或Task.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;}
}
我不確定為什么要避免使用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;
}
這樣,使用就好像它只是一個任務,但是初始化是惰性的,並且只會在需要時產生工作。
@Max 的回答很好,但我想添加建立在評論中提到的 Stephen 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();
}
SomePart 顯式繼承自Lazy<Task<>>
所以很明顯它是lazy和asyncronous 。
如果該工廠在真正的異步部分之前需要一些 CPU 繁重的工作,則調用基本構造函數將lazyPartFactory
包裝到Task.Run
以避免長阻塞。 如果不是你的情況,只需將其更改為base(lazyPartFactory)
SlowPart 可通過 TaskAwaiter 訪問。 所以 SomePart 的公共接口是:
var eagerValue = somePart.EagerPart;
var slowValue = await somePart;
使用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.NET和IObservable
因為在您的情況下,您將在處理不太天真的情況下在正確的時間開始您的任務時遇到更少的麻煩。 在我看來,它還使代碼更清晰
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
處理何時啟動任務,默認情況下它是 Lazy 並且每次訂閱時都會運行任務(這里 subscribe 是由啟用使用await
關鍵字的await
使用的)。
如果需要,您可以讓任務每分鍾(或永遠)只運行一次,並將其結果發布給所有訂閱者以節省性能,例如在現實世界的應用程序中,所有這些以及更多內容都由可觀察的。
聲明:
private Lazy<Task<ServerResult>> _lazyServerResult;`
ctor()
{
_lazyServerResult = new Lazy<Task<ServerResult>>(async () => await
GetServerResultAsync())
}
用法:
ServerResult result = await _lazyServerResult.Value;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.