Nếu giao diện của tôi phải trả về Nhiệm vụ, cách tốt nhất để thực hiện không hoạt động là gì?


435

Trong mã bên dưới, do giao diện, lớp LazyBarphải trả về một tác vụ từ phương thức của nó (và đối với các đối số không thể thay đổi). Nếu LazyBarviệc triển khai không bình thường ở chỗ nó chạy nhanh và đồng bộ - cách tốt nhất để trả về tác vụ Không hoạt động từ phương thức là gì?

Tôi đã đi với Task.Delay(0)bên dưới, tuy nhiên tôi muốn biết liệu điều này có bất kỳ tác dụng phụ hiệu suất nào không nếu chức năng được gọi rất nhiều (đối với lợi ích, nói hàng trăm lần một giây):

  • Liệu cú pháp đường này không gió đến một cái gì đó lớn?
  • Nó có bắt đầu làm tắc nghẽn nhóm chủ đề của ứng dụng của tôi không?
  • Là trình biên dịch cleaver đủ để đối phó với Delay(0)khác nhau?
  • Sẽ return Task.Run(() => { });khác nhau chứ?

Có cách nào tốt hơn?

using System.Threading.Tasks;

namespace MyAsyncTest
{
    internal interface IFooFace
    {
        Task WillBeLongRunningAsyncInTheMajorityOfImplementations();
    }

    /// <summary>
    /// An implementation, that unlike most cases, will not have a long-running
    /// operation in 'WillBeLongRunningAsyncInTheMajorityOfImplementations'
    /// </summary>
    internal class LazyBar : IFooFace
    {
        #region IFooFace Members

        public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
        {
            // First, do something really quick
            var x = 1;

            // Can't return 'null' here! Does 'Task.Delay(0)' have any performance considerations?
            // Is it a real no-op, or if I call this a lot, will it adversely affect the
            // underlying thread-pool? Better way?
            return Task.Delay(0);

            // Any different?
            // return Task.Run(() => { });

            // If my task returned something, I would do:
            // return Task.FromResult<int>(12345);
        }

        #endregion
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Test();
        }

        private static async void Test()
        {
            IFooFace foo = FactoryCreate();
            await foo.WillBeLongRunningAsyncInTheMajorityOfImplementations();
            return;
        }

        private static IFooFace FactoryCreate()
        {
            return new LazyBar();
        }
    }
}


8
Cá nhân tôi sẽ đi với Task.FromResult<object>(null).
CodeInChaos

Câu trả lời:


624

Sử dụng Task.FromResult(0)hoặc Task.FromResult<object>(null)sẽ phát sinh ít chi phí hơn so với việc tạo Taskbiểu thức không có op. Khi tạo một Taskkết quả được xác định trước, không có chi phí lập lịch liên quan.


Hôm nay, tôi khuyên bạn nên sử dụng Task.CompletedTask để thực hiện điều này.


5
Và nếu bạn tình cờ sử dụng github.com/StephenCleary/AsyncEx, họ sẽ cung cấp lớp TaskConstants để cung cấp các tác vụ đã hoàn thành này cùng với một số tác vụ khá hữu ích khác (0 int, true / false, Mặc định <T> ())
quentin-starin

5
return default(YourReturnType);
Huyền thoại

8
@Legends Điều đó không hoạt động để tạo một Nhiệm vụ trực tiếp
Reed Copsey

18
Tôi không chắc chắn nhưng Task.CompletedTaskcó thể làm các thủ thuật! (nhưng yêu cầu .net 4.6)
Peter

1
Câu hỏi: làm thế nào Task.FromResult<TResult>mà nó trả về a Task<TResult>, thỏa mãn kiểu trả về của Task(không có tham số loại)?
rory.ap

187

Để thêm vào câu trả lời của Reed Copsey về việc sử dụng Task.FromResult, bạn có thể cải thiện hiệu suất hơn nữa nếu bạn lưu trữ tác vụ đã hoàn thành vì tất cả các phiên bản của nhiệm vụ đã hoàn thành đều giống nhau:

public static class TaskExtensions
{
    public static readonly Task CompletedTask = Task.FromResult(false);
}

Với TaskExtensions.CompletedTaskbạn có thể sử dụng cùng một ví dụ trong toàn bộ miền ứng dụng.


Các phiên bản mới nhất của Net Framework (v4.6) cho biết thêm như vậy với các Task.CompletedTaskthuộc tính tĩnh

Task completedTask = Task.CompletedTask;

Tôi có cần phải trả lại hoặc chờ đợi nó không?
Pixar

@Pixar có ý gì? Bạn có thể làm cả hai, nhưng chờ nó sẽ tiếp tục đồng bộ.
i3arnon

Xin lỗi, tôi đã phải đề cập đến bối cảnh :) Như tôi thấy bây giờ, chúng ta có thể làm public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()tốt như vậy public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Vì vậy, chúng ta có thể return CompletedTask;hoặc await CompletedTask;. Điều gì là thích hợp hơn (có thể hiệu quả hơn hoặc phù hợp hơn)?
Pixar

3
@Pixar Tôi không rõ. Tôi có nghĩa là "'không đồng bộ" sẽ hiệu quả hơn ". Tạo một phương thức async hướng dẫn trình biên dịch chuyển đổi nó thành một máy trạng thái. Nó cũng sẽ tạo ra một nhiệm vụ mới mỗi khi bạn gọi nó. Trả lại một nhiệm vụ đã hoàn thành sẽ rõ ràng hơn và hiệu quả hơn.
i3arnon

3
@Asad nó làm giảm phân bổ (và với thời gian GC). Thay vì cấp phát bộ nhớ mới và xây dựng một thể hiện Nhiệm vụ mỗi khi bạn cần một Tác vụ hoàn thành, bạn chỉ thực hiện việc này một lần.
i3arnon

38

Task.Delay(0)như trong câu trả lời được chấp nhận là một cách tiếp cận tốt, vì nó là một bản sao được lưu trong bộ nhớ cache đã hoàn thành Task.

Cho đến ngày 4.6 Task.CompletedTask, mục đích của nó rõ ràng hơn, nhưng không chỉ Task.Delay(0)trả về một thể hiện được lưu trong bộ nhớ cache, nó còn trả về cùng một thể hiện được lưu trong bộ nhớ cache Task.CompletedTask.

Bản chất được lưu trong bộ nhớ cache của cả hai không được đảm bảo là không đổi, nhưng vì tối ưu hóa phụ thuộc vào triển khai chỉ phụ thuộc vào triển khai là tối ưu hóa (nghĩa là chúng vẫn hoạt động chính xác nếu việc triển khai thay đổi thành một thứ vẫn còn hiệu lực), việc sử dụng Task.Delay(0)là tốt hơn câu trả lời được chấp nhận.


1
Tôi vẫn đang sử dụng 4.5 và khi tôi thực hiện một số nghiên cứu, tôi đã thấy thú vị khi thấy rằng Task.Delay (0) được đặt ra đặc biệt để trả về một thành viên Hoàn thành tĩnh. Mà sau đó tôi lưu vào bộ nhớ cache trong thành viên Hoàn thành tĩnh của riêng tôi. : P
Darren Clark

2
Tôi không biết tại sao, nhưng Task.CompletedTaskkhông thể được sử dụng trong dự án PCL, ngay cả khi tôi đặt phiên bản .net thành 4.6 (hồ sơ 7), chỉ được thử nghiệm trong VS2017.
Felix

@Fay Tôi đoán nó không phải là một phần của bề mặt API PCL, mặc dù điều duy nhất làm bất cứ điều gì vào lúc này hỗ trợ PCL cũng hỗ trợ 4.5 vì vậy tôi đã phải sử dụng riêng của mình Task.CompletedTask => Task.Delay(0);để hỗ trợ điều đó, vì vậy tôi không Tôi không biết chắc chắn khỏi đỉnh đầu của tôi.
Jon Hanna

17

Gần đây đã gặp phải điều này và liên tục nhận được cảnh báo / lỗi về phương thức bị vô hiệu.

Chúng tôi đang kinh doanh trong việc xoa dịu trình biên dịch và điều này sẽ xóa nó:

    public async Task MyVoidAsyncMethod()
    {
        await Task.CompletedTask;
    }

Điều này tập hợp những điều tốt nhất của tất cả các lời khuyên ở đây cho đến nay. Không có tuyên bố trả lại là cần thiết trừ khi bạn thực sự làm một cái gì đó trong phương thức.


17
Điều đó là hoàn toàn sai. Bạn đang gặp lỗi trình biên dịch vì định nghĩa phương thức chứa async, do đó trình biên dịch chờ đợi. Việc sử dụng "chính xác" sẽ là Nhiệm vụ công khai MyVoidAsyncMethog () {return Task.CompletedTask;}
Keith

3
Không chắc chắn tại sao điều này đã được bỏ phiếu vì đây dường như là câu trả lời rõ ràng nhất
webwake

3
Vì bình luận của Keith.
noelicus

4
Anh ấy không hoàn toàn sai, anh ấy chỉ cần loại bỏ từ khóa async. Cách tiếp cận của tôi là thành ngữ hơn. Ông là người theo chủ nghĩa tối giản. Nếu không một chút thô lỗ.
Alexander Trauzzi

1
Điều này vô nghĩa, hoàn toàn đồng ý với Keith ở đây, tôi thực sự không nhận được tất cả các upvote. Tại sao bạn lại thêm mã không cần thiết? public Task MyVoidAsyncMethod() {}hoàn toàn giống như phương pháp trên. Nếu có một usecase để sử dụng nó như thế này, vui lòng thêm mã bổ sung.
Nick N.

12
return Task.CompletedTask; // this will make the compiler happy


3

Tôi thích Task completedTask = Task.CompletedTask;giải pháp của .Net 4.6, nhưng một cách tiếp cận khác là đánh dấu phương thức không đồng bộ và trả về void:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Bạn sẽ nhận được cảnh báo (CS1998 - Chức năng Async mà không cần chờ biểu hiện), nhưng điều này là an toàn để bỏ qua trong bối cảnh này.


1
Nếu phương thức của bạn trả về void, bạn có thể gặp vấn đề với các ngoại lệ.
Adam Tuliper - MSFT

0

Nếu bạn đang sử dụng thuốc generic, tất cả câu trả lời sẽ cho chúng tôi biên dịch lỗi. Bạn có thể sử dụng return default(T);. Mẫu dưới đây để giải thích thêm.

public async Task<T> GetItemAsync<T>(string id)
            {
                try
                {
                    var response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));
                    return response.Resource;
                }
                catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {

                    return default(T);
                }

            }

Tại sao các downvote?
Karthikeyan VK

Câu hỏi không phải là về các phương thức không đồng bộ :)
Frode Nilsen

0
return await Task.FromResult(new MyClass());

3
Mặc dù mã này có thể giải quyết câu hỏi, bao gồm giải thích về cách thức và lý do giải quyết vấn đề này thực sự sẽ giúp cải thiện chất lượng bài đăng của bạn và có thể dẫn đến nhiều lượt bình chọn hơn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ người hỏi bây giờ. Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng.
David Buck
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.