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


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.