Якщо мій інтерфейс повинен повернути Завдання, який найкращий спосіб не виконати операцію?


435

У наведеному нижче коді, завдяки інтерфейсу, клас LazyBarповинен повернути завдання зі свого методу (а для аргументів змінити не можна). Якщо LazyBarреалізація s незвична тим, що вона запускається швидко і синхронно - який найкращий спосіб повернути завдання без операції з методу?

Я пішов з Task.Delay(0)нижче, однак я хотів би знати, чи є у цього побічні ефекти від продуктивності, якщо функція викликається багато (для аргументів, скажіть сотні разів в секунду):

  • Чи синтаксичний цукор розмовляє щось велике?
  • Чи почне вона засмічувати пул потоків моєї програми?
  • Чи достатньо чіткого компілятора для вирішення Delay(0)інших питань?
  • Було return Task.Run(() => { });б інакше?

Чи є кращий спосіб?

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

2
Пов'язаний з цим питання: stackoverflow.com/questions/4245968/create-a-completed-task
— CodesInChaos

8
Особисто я б пішов з цим Task.FromResult<object>(null).
— CodesInChaos

Відповіді:


625

Використання Task.FromResult(0)або Task.FromResult<object>(null)нестиме накладні витрати, ніж створювати Taskвираз без відключення. При створенні Taskзаздалегідь визначеного результату не передбачено накладних накладних витрат.


Сьогодні я рекомендую використовувати Task.CompletedTask для цього.


5
І якщо ви випадково використовуєте github.com/StephenCleary/AsyncEx, вони забезпечують клас TaskConstants для надання цих завершених завдань разом з кількома іншими досить корисними (0 int, true / false, Default <T> ())
— quentin-starin

5
return default(YourReturnType);
— Легенди

8
@Legends Це не працює для створення завдання безпосередньо
— Reed Copsey

18
я не впевнений, але Task.CompletedTaskможе зробити трюк! (але вимагає .net 4.6)
— Петро

1
Питання: як це Task.FromResult<TResult>, що повертає a Task<TResult>, задовольняє тип повернення Task(без парамерів типу)?
— rory.ap

187

Щоб додати відповідь Рід Копсі про використання Task.FromResult, ви можете ще більше покращити продуктивність, якщо кешувати вже виконане завдання, оскільки всі екземпляри виконаних завдань однакові:

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

З TaskExtensions.CompletedTaskви можете використовувати один і той же екземпляр на протязі всього домена додатку.


Остання версія .Net Framework (v4.6) додає тільки що з Task.CompletedTaskстатичним властивістю

Task completedTask = Task.CompletedTask;

Мені потрібно повернути його або чекати на нього?
— Піксар

@Pixar, що ти маєш на увазі? Можна зробити і те, і інше, але очікуючи, що це продовжиться синхронно.
— i3arnon

Вибачте, я повинен був згадати контекст :) Як я це бачу зараз, ми можемо зробити public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()так само public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations(). Отже, ми можемо return CompletedTask;або await CompletedTask;. Що є більш переважним (можливо, більш ефективним чи більш узгодженим)?
— Піксар

3
@Pixar Я не хочу зрозуміти. Я мав на увазі, що "no-async" буде ефективнішим ". Здійснення методу async вказує компілятору перетворити його на стан-машину. Він також створюватиме нове завдання щоразу, коли ви його закликаєте. Повернення вже виконаного завдання було б зрозумілішим та ефективнішим.
— i3arnon

3
@Asad це зменшує асигнування (а з ним і GC час). Замість того, щоб виділяти нову пам'ять та створювати екземпляр Завдання щоразу, коли вам потрібна виконана задача, ви робите це лише один раз.
— i3arnon

38

Task.Delay(0)як у прийнятій відповіді був хорошим підходом, оскільки це кешована копія заповненої Task.

Оскільки в 4.6 зараз Task.CompletedTaskє більш чітке за своїм призначенням, але не тільки Task.Delay(0)все одно повертає один кешований екземпляр, він повертає той самий один кешований екземпляр, як і він Task.CompletedTask.

Кешуються природа ні гарантовано залишається постійна, але залежить від реалізації оптимізацій, які тільки залежать від реалізації , як оптимізації (тобто, вони по- , як і раніше працювати правильно , якщо реалізація змінені на те , що було до цих пір в силі) використання Task.Delay(0)було краще, ніж прийнята відповідь.


1
Я все ще використовую 4.5, і коли я робив деякі дослідження, я розважався, виявивши, що Task.Delay (0) є спеціальним випадком, щоб повернути статичний член CompletedTask. Який потім кешував у своєму власному статичному члені CompletedTask. : P
— Даррен Кларк

2
Я не знаю чому, але Task.CompletedTaskне можу бути використаним у проекті PCL, навіть якщо я встановив .net версію 4.6 (профіль 7), щойно перевірена в VS2017.
— Фелікс

@Fay, я думаю, він не повинен бути частиною поверхні PCL API, хоча єдине, на що в даний момент робити що-небудь, що підтримує PCL, також підтримує 4.5, тому мені вже доводиться використовувати своє, Task.CompletedTask => Task.Delay(0);щоб підтримати це, тому я не хочу Я точно не знаю, що з голови.
— Джон Ханна

17

Нещодавно зіткнулися з цим і постійно отримували попередження / помилки щодо недійсного методу.

Ми займаємося розміщенням компілятора, і це очищає його:

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

Це поєднує в собі найкращі з усіх порад тут. Заява про повернення не потрібна, якщо ви дійсно щось не робите в методі.


17
Це абсолютно неправильно. Ви отримуєте помилку компілятора, оскільки визначення методу містить асинхронізацію, тому компілятор очікує очікування. "Правильним" використанням буде загальнодоступне завдання MyVoidAsyncMethog () {return Task.CompletedTask;}
— Кіт,

3
Не впевнений, чому за це було проголосовано, оскільки, здається, це найчистіша відповідь
— веб-пробудження

3
Через коментар Кіта.
— noelicus

4
Він абсолютно не помиляється, він просто видалив ключове слово async. Мій підхід більш ідіоматичний. Його мінімалістичний. Якщо не трохи грубо.
— Олександр Трауцзі

1
Це не має сенсу, повністю згоден з Кітом тут, я фактично не отримую всіх відгуків. Чому б ви додали код, який не потрібен? public Task MyVoidAsyncMethod() {}абсолютно такий же, як і вищевказаний метод. Якщо є така шафа для її використання, будь ласка, додайте додатковий код.
— Нік Н.


9

Коли потрібно повернути вказаний тип:

Task.FromResult<MyClass>(null);

3

Я віддаю перевагу Task completedTask = Task.CompletedTask;рішенню .Net 4.6, але інший підхід полягає в позначенні методу асинхронізацією та поверненням недійсним:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

Ви отримаєте попередження (CS1998 - функція Async без очікування виразу), але це безпечно ігнорувати в цьому контексті.


1
Якщо ваш метод повернеться недійсним, у вас можуть виникнути проблеми з винятками.
— Адам Туліпер - MSFT

0

Якщо ви використовуєте дженерики, усі відповіді дадуть нам помилку компіляції. Можна використовувати return default(T);. Зразок нижче, щоб пояснити далі.

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

            }

Чому потік?
— Картікеян ВК

Питання було не про методи асинхронізації :)
— Frode Nilsen

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

3
Хоча цей код може вирішити питання, включаючи пояснення, як і чому це вирішує проблему, справді допоможе покращити якість вашої публікації та, ймовірно, призведе до збільшення кількості голосів. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз задає питання. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
— Девід Бак
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.