2014-11-05 20 views
27

Tôi có một giao diệnCách triển khai phương thức giao diện trả về tác vụ <T>?

interface IFoo 
{ 
    Task<Bar> CreateBarAsync(); 
} 

Có hai phương pháp để tạo ra Bar, một không đồng bộ và một đồng bộ. Tôi muốn cung cấp một giao diện thực hiện cho mỗi một trong hai phương pháp này.

Đối với phương pháp không đồng bộ, việc thực hiện có thể nhìn như thế này:

class Foo1 : IFoo 
{ 
    async Task<Bar> CreateBarAsync() 
    { 
    return await AsynchronousBarCreatorAsync(); 
    } 
} 

Nhưng CÁCH tôi nên thực hiện các lớp Foo2 mà sử dụng đồng bộ phương pháp để tạo Bar?

tôi thể thực hiện các phương pháp để chạy đồng bộ:

async Task<Bar> CreateBarAsync() 
    { 
    return SynchronousBarCreator(); 
    } 

thì Trình biên dịch sẽ cảnh báo chống lại bằng async trong phương pháp chữ ký:

phương pháp

async này thiếu 'await' khai thác và sẽ chạy đồng bộ. Xem xét sử dụng toán tử 'await' để đợi các cuộc gọi API không chặn hoặc 'đang chờ Task.Run (...)' để thực hiện công việc liên kết CPU trên một chuỗi nền.

Hoặc, tôi có thể thực hiện phương thức để trả lại rõ ràng Task<Bar>. Theo ý kiến ​​của tôi, mã sẽ trông ít có thể đọc được:

Task<Bar> CreateBarAsync() 
    { 
    return Task.Run(() => SynchronousBarCreator()); 
    } 

Từ quan điểm hiệu năng, tôi cho rằng cả hai phương pháp đều có cùng chi phí, hoặc?

Tôi nên chọn phương pháp nào; thực hiện phương thức async đồng bộ hoặc kết nối rõ ràng cuộc gọi phương thức đồng bộ trong Task?

EDIT

Dự án tôi đang làm việc trên thực sự là một dự án 4 NET với async/chờ phần mở rộng từ gói Microsoft Async NuGet. Trên .NET 4, Task.Run sau đó có thể được thay thế bằng TaskEx.Run. Tôi đã sử dụng phương thức .NET 4.5 một cách có ý thức trong ví dụ trên với hy vọng làm cho câu hỏi chính rõ ràng hơn.

+2

Trong trường hợp này, tôi tin 'Task.FromResult (SynchronousBarCreator())' là tốt hơn 'Task.Run (...)' vì nó không thực sự lên lịch và chạy một tác vụ để có được kết quả. –

+0

Có phải 'SynchronousBarCreator' chạy dài không? Có phải CPU bị ràng buộc, hay nó dành thời gian chờ đợi cái gì khác? – CodesInChaos

+0

Trong trường hợp tôi đang làm việc ngay bây giờ, nó là * không * chạy dài. Nhưng nó có thể là trong các kịch bản sắp tới. –

Trả lời

16

Khi bạn phải thực hiện một phương pháp async từ một giao diện và thực hiện của bạn là đồng bộ, bạn có thể sử dụng Ned của giải pháp:

public Task<Bar> CreateBarAsync() 
{ 
    return Task.FromResult<Bar>(SynchronousBarCreator()); 
} 

Với giải pháp này, phương pháp này trông async nhưng là đồng bộ.

Hoặc giải pháp bạn đề xuất:

Task<Bar> CreateBarAsync() 
    { 
    return Task.Run(() => SynchronousBarCreator()); 
    } 

Bằng cách này phương pháp này là thực sự async.

Bạn không có giải pháp chung chung phù hợp với tất cả các trường hợp "Cách triển khai phương thức giao diện trả về tác vụ". Nó phụ thuộc vào ngữ cảnh: việc triển khai của bạn có đủ nhanh để gọi nó trên một luồng khác là vô ích không? Giao diện này được sử dụng như thế nào khi phương thức này được gọi (nó sẽ đóng băng ứng dụng)? Thậm chí có thể gọi triển khai của bạn trong một chuỗi khác không?

+0

Cảm ơn bạn đã tóm tắt rất tốt, Guillaume. Thậm chí có một bối cảnh mà bạn muốn giới thiệu triển khai đầu tiên của tôi ('async Task CreateBarAsync() {return SynchronousBarCreator();} ') hay phương pháp đó * không bao giờ * được khuyến khích không? –

+11

Bạn không nên sử dụng [* async over sync *] (http://blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx) –

+1

'Task.Run' gần như luôn luôn kết thúc đã sử dụng. Thực sự chỉ có một lý do để sử dụng nó và đó là khi bạn có tính toán trên một ứng dụng GUI. Nhưng rất hiếm khi có các nhiệm vụ chuyên sâu về tính toán (liên quan đến CPU). – Aron

18

Hãy thử điều này:

class Foo2 : IFoo 
{ 
    public Task<Bar> CreateBarAsync() 
    { 
     return Task.FromResult<Bar>(SynchronousBarCreator()); 
    } 
} 

Task.FromResult tạo ra một nhiệm vụ đã hoàn thành của các loại quy định sử dụng các giá trị cung cấp.

+1

Rất cám ơn, Ned, điều này có vẻ như là một giải pháp rất tốt cho kịch bản của tôi. Tôi cũng đang tìm kiếm một giải pháp có thể hoạt động.NET 4 với phần mở rộng * async *, và may mắn thay nó chỉ ra rằng lớp 'TaskEx' cũng chứa phương thức 'FromResult'. Vì vậy, tất cả trong tất cả, tôi rất hài lòng với giải pháp này. –

+3

Vui vì tôi đã có thể giúp đỡ –

+0

Xin chào mọi người, tại sao bạn đánh giá câu trả lời này? Phương thức này chạy đồng bộ nhưng có hậu tố Async và trả về Tác vụ, điều này hoàn toàn không chính xác. Bạn nên trả lại Task.Run (SynchronousBarCreator) để làm cho nó không đồng bộ. –

7

Nếu bạn đang sử dụng .NET 4.0, bạn có thể sử dụng TaskCompletionSource<T>:

Task<Bar> CreateBarAsync() 
{ 
    var tcs = new TaskCompletionSource<Bar>(); 
    tcs.SetResult(SynchronousBarCreator()); 
    return tcs.Task 
} 

Cuối cùng, nếu theres không có gì không đồng bộ về phương pháp của bạn, bạn nên cân nhắc để lộ một thiết bị đầu cuối đồng bộ (CreateBar) mà tạo ra một mới Bar.Bằng cách đó không có bất ngờ và không cần phải bọc với một dư thừa Task.

+0

Rất cám ơn, Yuval, cũng rất hay biết. Điều này thậm chí sẽ hoạt động mà không có phần mở rộng * async * từ gói * Gói Microsoft Async * NuGet, phải không? Tôi tuy nhiên bao gồm các phần mở rộng * async * trong dự án .NET 4 của tôi, và sau đó phương thức 'TaskEx.FromResult' có vẻ là cách tiếp cận hấp dẫn hơn. –

+3

Có, bạn có thể sử dụng nó mà không cần bất kỳ phần mở rộng nào. 'Task.FromResult' và' TaskEx.FromResult' chỉ đơn giản là một trình bao bọc xung quanh một 'TaskCompletionSource' –

+0

Có một trường hợp để chuỗi' Task.Run' (hoặc đang chờ một Task.Delay, hoặc sử dụng ContinueWith), nếu phương thức này sẽ không đồng bộ trong tương lai - không thực hiện nó không đồng bộ ngay bây giờ có thể tạo ra lệnh thực thi không thể đoán trước hoặc gây ra các điều kiện chủng tộc. –

5

Để bổ sung cho những người khác câu trả lời, có một lựa chọn hơn, mà tôi tin rằng công trình với .NET 4.0 quá:

class Foo2 : IFoo 
{ 
    public Task<Bar> CreateBarAsync() 
    { 
     var task = new Task<Bar>(() => SynchronousBarCreator()); 
     task.RunSynchronously(); 
     return task; 
    } 
} 

Note task.RunSynchronously(). Nó có thể là tùy chọn chậm nhất, so với Task<>.FromResultTaskCompletionSource<>.SetResult, nhưng có sự khác biệt quan trọng nhưng quan trọng: hành vi truyền bá lỗi.

Cách tiếp cận trên sẽ bắt chước hành vi của phương pháp async, trong đó ngoại lệ không bao giờ được ném trên cùng một khung ngăn xếp (tháo ra), nhưng được lưu trữ không hoạt động bên trong đối tượng Task. Người gọi thực sự phải quan sát nó qua await task hoặc task.Result, tại thời điểm đó người gọi sẽ được quay lại.

Đây không phải là trường hợp với Task<>.FromResultTaskCompletionSource<>.SetResult, trong đó bất kỳ ngoại lệ nào được ném bởi SynchronousBarCreator sẽ được truyền trực tiếp đến người gọi, thư giãn ngăn xếp cuộc gọi.

Tôi có giải thích chi tiết hơn một chút về điều này ở đây:

Any difference between "await Task.Run(); return;" and "return Task.Run()"?

Trên một mặt lưu ý, tôi đề nghị bổ sung một điều khoản cho hủy khi thiết kế giao diện (ngay cả khi hủy hiện không sử dụng/triển khai):

interface IFoo 
{ 
    Task<Bar> CreateBarAsync(CancellationToken token); 
} 

class Foo2 : IFoo 
{ 
    public Task<Bar> CreateBarAsync(CancellationToken token) 
    { 
     var task = new Task<Bar>(() => SynchronousBarCreator(), token); 
     task.RunSynchronously(); 
     return task; 
    } 
} 
+1

Thực sự tốt để biết, Noseratio, cảm ơn nhiều vì thông tin bổ sung này! –

+0

Nếu bạn ném ngoại lệ trước khi chờ trong một phương thức không đồng bộ, ngoại lệ sẽ được ném lên cùng một ngăn xếp hoặc cũng được lưu trữ? – Guillaume

+0

@Guillaume, vâng, ngoại lệ cũng sẽ được lưu trữ, ngay cả khi được ném từ phần đồng bộ của phương thức 'async'. – Noseratio

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