Придушення "попередження CS4014: Оскільки цього дзвінка не чекають, виконання поточного методу продовжується ..."


156

Це не дублікат "Як безпечно викликати метод асинхронізації в C # без очікування" .

Як мені красиво придушити наступне попередження?

попередження CS4014: Оскільки цього дзвінка не чекають, виконання поточного методу продовжується до завершення виклику. Подумайте про застосування оператора "в очікуванні" до результату дзвінка.

Простий приклад:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Що я спробував і що не сподобалось:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Оновлено , оскільки оригінальну прийняту відповідь було відредаговано, я змінив прийняту відповідь на відповідну, використовуючи відкидання C # 7.0 , оскільки не думаю ContinueWith, що тут доречно. Кожного разу, коли мені потрібно реєструвати винятки для операцій із забудовою, то тут я використовую більш детальний підхід, запропонований Стівеном Клірі .


1
Отже, ти вважаєш, що #pragmaце не приємно?
Фредерік Хаміді

10
@ FrédéricHamidi, я.
носраціо

2
@Noseratio: Ага, правильно. Вибачте, я думав, що це було інше попередження. Ігнорувати мене!
Джон Скіт

3
@Terribad: Я не дуже впевнений - здається, що попередження є досить розумним у більшості випадків. Зокрема, ви повинні подумати про те, що ви хочете статися з будь-якими невдачами - зазвичай навіть для "пожежі та забудьте" ви повинні розробити, як зареєструвати помилки тощо.
Джон Скіт,

4
@Terribad, перш ніж використовувати його так чи інакше, ви повинні мати чітке уявлення про поширення винятків для методів асинхронізації (перевірте це ). Тоді, відповідь @ Kna provideis надає елегантний спосіб не втратити винятків для вогню та забуття за допомогою async voidдопоміжного методу.
нісраціо

Відповіді:


160

З C # 7 тепер ви можете використовувати викиди :

_ = WorkAsync();

7
Це зручна маленька мовна функція, яку я просто не можу згадати. Це все одно, що є _ = ...в моєму мозку.
Марк Л.

3
Я виявив, що SupressMessage видалив своє попередження зі своєї візуальної студії "Список помилок", але не "Вихід" і #pragma warning disable CSxxxxвиглядає більш потворно, ніж відкинути;)
Девід Савадж,

122

Ви можете створити метод розширення, який запобіжить попередження. Метод розширення може бути порожнім або ви можете додати .ContinueWith()там обробку винятків .

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

Однак ASP.NET підраховує кількість запущених завдань, тому він не працюватиме з простим Forget()розширенням, як зазначено вище, і натомість може вийти з ладу за винятком:

Асинхронний модуль або обробник завершено, поки асинхронна операція ще не завершена.

За допомогою .NET 4.5.2 це можна вирішити за допомогою HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}

8
Я знайшов TplExtensions.Forget. Там набагато більше добра Microsoft.VisualStudio.Threading. Я хочу, щоб він став доступним для використання поза Visual Studio SDK.
носраціо

1
@Noseratio та Knagis, мені подобається такий підхід і я планую його використовувати. Я розмістив відповідне додаткове запитання: stackoverflow.com/questions/22864367/fire-and-forget-approach
Метт Сміт

3
@stricq Якій меті слугуватиме додавання ConfigureAwait (false) до Forget ()? Як я розумію, ConfigureAwait впливає лише на синхронізацію потоку в тій точці, де очікування використовується у завданні, але мета Forget () - відкинути завдання, тому Завдання ніколи не чекати, тому ConfigureAwait тут безглуздо.
дторп

3
Якщо нитка нересту відходить до того, як завдання вогню і забуття буде завершено, без ConfigureAwait (false) вона все одно намагатиметься повернутись на нерестовий потік, ця нитка вже не залишилася, щоб вона зайшла в тупики. Налаштування ConfigureAwait (false) вказує системі не повертатись на викликовий потік.
stricq

2
Ця відповідь має редагування для керування конкретним випадком, а також десяток коментарів. Просто речі часто є правильними речами, ідіть на скидання! І я цитую відповідь @ fjch1997: Нерозумно створювати метод, який потребує виконання ще декількох галочок, лише з метою придушення попередження.
Teejay

39

Можна прикрасити метод наступним атрибутом:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

В основному ви говорите компілятору, що ви знаєте, що робите, і не потрібно турбуватися про можливу помилку.

Важливою частиною цього коду є другий параметр. Частина "CS4014:" - це те, що пригнічує попередження. На решті можна написати що завгодно.


Для мене не працює: Visual Studio для Mac 7.0.1 (збірка 24). Здається, як слід, але - ні.
IronRod

1
[SuppressMessage("Compiler", "CS4014")]пригнічує повідомлення у вікні списку помилок, але у вікні виводу все ще відображається попереджувальна лінія
Девід Чінг

35

Мій два способи впоратися з цим.

Збережіть її у змінній змінній (C # 7)

Приклад

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

З моменту введення скидів у C # 7, я вважаю це кращим, ніж придушення попередження. Тому що він не тільки пригнічує попередження, але й дає зрозуміти намір пожежі та забути.

Більше того, компілятор зможе оптимізувати його у режимі випуску.

Просто придуши це

#pragma warning disable 4014
...
#pragma warning restore 4014

є досить приємним рішенням "стріляти і забути".

Причина, чому існує таке попередження, полягає в тому, що в багатьох випадках ви не маєте наміру використовувати метод, який повертає завдання, не чекаючи його. Придушення попередження, коли ви дійсно маєте намір стріляти і забути, має сенс.

Якщо у вас виникли проблеми з запам'ятовуванням написання #pragma warning disable 4014, просто дозвольте Visual Studio додати його для вас. Натисніть Ctrl +. відкрити "Швидкі дії", а потім "Придушити CS2014"

Загалом

Дурно створити метод, який потребує виконання ще декількох кліщів, лише з метою придушення попередження.


Це працювало в Visual Studio для Mac 7.0.1 (збірка 24).
IronRod

1
Нерозумно створювати метод, який вимагає виконувати ще кілька галочок, лише з метою придушення попередження - цей взагалі не додає зайвих кліщів, і IMO є більш читабельним:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio

1
@Noseratio Дуже багато разів, коли я використовую AggressiveInliningкомпілятор просто ігнорує його з будь-якої причини
fjch1997

1
Мені подобається варіант прагми, тому що він дуже простий і стосується лише поточного рядка (або розділу) коду, а не цілого методу.
wasatchwizard

2
Не забудьте використати код помилки, як, #pragma warning disable 4014а потім відновити попередження за допомогою #pragma warning restore 4014. Він все ще працює без коду помилки, але якщо ви не додасте номер помилки, він припинить усі повідомлення.
DunningKrugerEffect

11

Простий спосіб зупинки попередження - це просто призначити завдання під час виклику:

Task fireAndForget = WorkAsync(); // No warning now

І так у своєму початковому дописі ви зробите:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Цей підхід я згадував у самому питанні, як один із тих, хто мені особливо не подобався.
носраціо

Ого! Не помічав цього, бо він знаходився в тому ж розділі коду, що і ваша прагма ... І я шукав відповіді. Крім цього, що вам не подобається у цьому методі?
noelicus

1
Мені не подобається, що це taskвиглядає як забута локальна змінна. Майже як компілятор повинен дати мені ще одне попередження, щось на кшталт " taskпризначається, але його значення ніколи не використовується", до того ж це не так. Крім того, це робить код менш читабельним. Я сам використовую такий підхід.
носраціо

Досить справедливо - у мене було подібне відчуття, і саме тому я його називаю fireAndForget... тому я очікую, що воно відтепер буде невирішеним.
noelicus

4

Причиною попередження є те, що WorkAsync повертає інформацію, Taskяку ніколи не читають і не чекають. Ви можете встановити тип повернення WorkAsync на, voidі попередження зніметься .

Зазвичай метод повертає a, Taskколи абонент повинен знати статус працівника. У випадку пожежі та забуття, недійсність повинна повернутися так, щоб нагадувати, що абонент не залежить від викликаного методу.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

2

Чому б не загорнути його всередину методу асинхронізації, який повертає порожнечу? Трохи тривалий, але використовуються всі змінні.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}

1

Я знайшов такий підхід сьогодні випадково. Ви можете визначити делегата та призначити спочатку делегат метод асинхронізації.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

і називати це так

(new IntermediateHandler(AsyncOperation))();

...

Я подумав, що цікаво, що компілятор не видасть таке саме попередження при використанні делегата.


Не потрібно декларувати делегата, можливо, ви також можете (new Func<Task>(AsyncOperation))()це зробити, хоча IMO все ще дещо багатослівний.
носраціо
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.