2017-07-24 16 views
15

Tôi có một tình huống mà tôi có một cây đối tượng được tạo ra bởi một nhà máy đặc biệt. Điều này là hơi giống với một container DI, nhưng không hoàn toàn.Force C# nhiệm vụ không đồng bộ để được lười biếng?

Tạo đối tượng luôn xảy ra thông qua hàm tạo và đối tượng không thay đổi.

Một số phần của cây đối tượng có thể không cần thiết trong một thực thi nhất định và phải được tạo ra một cách lười biếng. Vì vậy, các đối số constructor nên là một cái gì đó mà chỉ là một nhà máy để tạo theo yêu cầu. Điều này trông giống như một công việc cho Lazy.

Tuy nhiên, việc tạo đối tượng có thể cần truy cập tài nguyên chậm và do đó luôn không đồng bộ. (Chức năng tạo của nhà máy đối tượng trả về một Task.) Điều này có nghĩa là chức năng tạo cho Lazy sẽ cần phải không đồng bộ, và do đó loại được tiêm cần phải là Lazy<Task<Foo>>.

Nhưng tôi không muốn có gói kép. Tôi tự hỏi liệu có thể ép buộc Task trở nên lười biếng, tức là tạo ra một Task được đảm bảo không thực thi cho đến khi nó được chờ đợi. Theo tôi hiểu, Task.Run hoặc Task.Factory.StartNew có thể bắt đầu thực hiện bất kỳ lúc nào (ví dụ: nếu chuỗi từ hồ bơi ở chế độ rảnh), ngay cả khi không có gì đang chờ.

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

hữu ích này? https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/ – DavidG

+0

@DavidG Cảm ơn, tôi đã đọc bài viết này trước đây. Đây sẽ là cách giải quyết mà tôi sẽ sử dụng nếu tôi không thể tạo ra một 'Task' đơn giản, nhưng tôi vẫn hy vọng có một cách để làm điều đó. –

+2

Chờ đợi sẽ không bắt đầu một nhiệm vụ cho bạn nếu nó chưa được khởi động, vì vậy bạn không thể "tạo một Tác vụ được đảm bảo không thực thi cho đến khi nó được chờ đợi" (trừ khi bạn tự khởi động nó trước khi chờ đợi xảy ra). – Evk

Trả lời

17

Tôi không chắc chắn chính xác lý do tại sao bạn muốn tránh sử dụng Lazy<Task<>>,, nhưng nếu nó chỉ là để giữ cho các API dễ dàng hơn để sử dụng, vì đây là một thuộc tính, bạn có thể làm điều đó với một lĩnh vực sao lưu:

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; 
} 

Bằng cách đó, việc sử dụng như thể đó chỉ là một nhiệm vụ, nhưng việc khởi tạo là lười biếng và sẽ chỉ phát sinh công việc nếu cần.

0

Sử dụng constructor cho Task làm nhiệm vụ lười biếng aka không chạy cho đến khi bạn nói nó để chạy, vì vậy bạn có thể làm một cái gì đó như thế này:

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; 
} 

Kết quả:

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

Tuy nhiên Tôi khuyên bạn nên sử dụng Rx.NETIObservable như trong trường hợp của bạn, bạn sẽ nhận được ít phiền hà hơn để xử lý ít hơn những trường hợp ngây thơ để bắt đầu công việc của bạn vào đúng thời điểm. Ngoài ra nó làm cho đoạn code một chút bụi theo ý kiến ​​của tôi

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; 
} 

Kết quả:

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

Để được rõ ràng hơn: Các Observable đây xử lý khi bắt đầu công việc, đó là Lazy theo mặc định và sẽ chạy nhiệm vụ mọi lúc nó được đăng ký (ở đây đăng ký được sử dụng bởi các awaiter cho phép sử dụng các từ khóa await). Bạn có thể, nếu bạn cần, làm cho nhiệm vụ chỉ chạy một lần mỗi phút (hoặc bao giờ) và có kết quả được công bố trên tất cả người đăng ký để tiết kiệm hiệu suất chẳng hạn như trong ứng dụng thế giới thực, tất cả điều này và nhiều nhiều hơn được xử lý bởi các quan sát.

+1

'Tôi khuyên bạn nên sử dụng Rx.NET và IObservable' - đó là những gì tôi gọi là quá mức cần thiết cho vấn đề cụ thể này. Khả năng phản ứng rất thú vị nhưng tôi sẽ giữ nó trong một thời gian khác – pkuderov

+0

Nó không phải là quá mức cần thiết nếu bạn không bỏ qua tất cả số lượng các vấn đề cụ thể mà bạn phải xử lý hàng ngày trong một ứng dụng thế giới thực. Bạn có thể tranh luận điều này giống như điều "sử dụng jquery" cũ tốt, và nó là bằng cách nào đó, nhưng tôi thực sự cảm thấy như OP kịch bản có liên quan đến điều này. Thêm vào đó tôi đã trả lời câu hỏi của anh ta mà không có Observables, tôi đã đề cập đến nó như một thứ anh ta nên sử dụng. – Uwy

+0

đó là lý do tại sao tốt hơn là không tạo ra nhiều vấn đề hơn nữa và tăng entropy bằng cách chọn các công cụ không đúng, imho – pkuderov

2

@Max 'Câu trả lời là tốt nhưng tôi muốn thêm phiên bản được xây dựng trên đầu trang của Stephen Toub' bài báo đề cập trong nhận xét:

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 của thừa hưởng một cách rõ ràng từ Lazy<Task<>> vì vậy nó rõ ràng rằng đó là lười biếngasyncronous.

  2. Gọi hàm xây dựng cơ bản kết thúc tốt đẹp lazyPartFactory đến Task.Run để tránh chặn dài nếu nhà máy đó cần một số công việc nặng nhọc trước khi thực phần không đồng bộ. Nếu không phải trường hợp của bạn, chỉ cần thay đổi thành base(lazyPartFactory)

  3. Có thể truy cập SlowPart thông qua TaskAwaiter. Vì vậy, giao diện công cộng SomePart' là:

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

Có lẽ không phải là câu trả lời @Max tốt hơn vì sử dụng thành phần thay vì thừa kế? IsValueCreated có thể được hiểu sai trong ngữ cảnh API, không? –

+0

vâng, hoàn toàn đồng ý – pkuderov

Các vấn đề liên quan