Придушити попередження CS1998: цього методу асинхронізації бракує "очікування"


104

У мене є інтерфейс з деякими функціями асинхронізації. Деяким класам, що реалізує інтерфейс, чекати нічого не можна, а деякі можуть просто кинути. Це трохи дратує всі попередження.

Якщо ви не використовуєте функцію очікування у функції асинхронізації.

Чи можливо придушити повідомлення?

public async Task<object> test()
{
    throw new NotImplementedException();
}

попередження CS1998: Цей метод асинхронізації не має операторів "очікування" і запускатиметься синхронно. Подумайте про використання оператора "очікувати" для очікування неблокуючих викликів API або "очікування Task.Run (...)" для виконання роботи, пов'язаної з процесором, у фоновому потоці.


1
Коли не використовується нове ключове слово очікування у функції, позначеній як асинхронізація.
Симон

Як щодо показу нам зразка коду, який відтворює проблему?
Джон Сондерс

Відповіді:


106

У мене є інтерфейс з деякими функціями асинхронізації.

TaskЯ вважаю, що методи повертаються . asyncє деталізацією реалізації, тому її не можна застосувати до методів інтерфейсу.

Деяким класам, що реалізує інтерфейс, чекати нічого не можна, а деякі можуть просто кинути.

У цих випадках ви можете скористатися тим фактом, що asyncє деталізацією.

Якщо у вас немає нічого await, тоді ви можете просто повернутися Task.FromResult:

public Task<int> Success() // note: no "async"
{
  ... // non-awaiting code
  int result = ...;
  return Task.FromResult(result);
}

У випадку з кидком NotImplementedExceptionпроцедура дещо словніша:

public Task<int> Fail() // note: no "async"
{
  var tcs = new TaskCompletionSource<int>();
  tcs.SetException(new NotImplementedException());
  return tcs.Task;
}

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

public static class TaskConstants<TResult>
{
  static TaskConstants()
  {
    var tcs = new TaskCompletionSource<TResult>();
    tcs.SetException(new NotImplementedException());
    NotImplemented = tcs.Task;
  }

  public static Task<TResult> NotImplemented { get; private set; }
}

public Task<int> Fail() // note: no "async"
{
  return TaskConstants<int>.NotImplemented;
}

Клас хелперів також зменшує сміття, яке в іншому випадку GC повинен був би зібрати, оскільки кожен метод з тим самим типом повернення може поділяти його Taskта NotImplementedExceptionоб'єкти.

У моїй бібліотеці AsyncEx у мене є кілька інших прикладів типу "константа завдань" .


1
Я не думав втрачати ключове слово. Як ви кажете, async не має нічого спільного з інтерфейсом. Моє погано, дякую.
Симон

3
Чи можете ви порекомендувати підхід, коли тип повернення - це просто завдання (без результату?)
Майк

9
Попередження: Цей підхід може спричинити проблеми, оскільки помилки не поширюватимуться так, як ви очікуєте. Зазвичай абонент очікує, що у вашому методі буде виняток у межах Task. Натомість ваш метод кине, перш ніж він навіть отримає шанс створити Task. Я дійсно думаю, що найкраща модель - це визначити asyncметод без awaitоператорів. Це забезпечує код у методі, який усі трактуються як частина Task.
Боб Майєрс

11
Щоб уникнути CS1998, ви можете додати await Task.FromResult(0);свій метод. Це не повинно мати суттєвого впливу на перф (на відміну від Task.Yield ()).
Боб Майєрс

3
@AndrewTheken: Ці дні ти можеш просто зробити return Task.CompletedTask;- найпростіший з усіх.
Стівен Клірі

63

Інший варіант, якщо ви хочете, щоб тіло функції було простим, а не писали код для його підтримки, - це просто придушити попередження за допомогою #pragma:

#pragma warning disable 1998
public async Task<object> Test()
{
    throw new NotImplementedException();
}
#pragma warning restore 1998

Якщо це досить поширено, ви можете помістити оператор відключення у верхню частину файлу та пропустити відновлення.

http://msdn.microsoft.com/en-us/library/441722ys(v=vs.110).aspx


41

Ще один спосіб збереження ключового слова async (у випадку, якщо ви хочете його зберегти) - це використовувати:

public async Task StartAsync()
{
    await Task.Yield();
}

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


Це має бути прийнятою відповіддю. Іноді реалізації інтерфейсу не потрібно асинхронізувати, це набагато чіткіше, ніж загортати все у Task.Runвиклик.
Ендрю Текен

12
очікувати Завдання.Завершене завдання; // може бути кращий варіант
Фрод Nilsen

@FrodeNilsen чомусь Task.CompletedTask, здається, вже не існує.
Себастьян Ванштенкісте

1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, .Net Core 1.0->
Frode Nilsen

1
@AndrewTheken У мене знадобився певний час, щоб дійти висновку, що ця відповідь та ваш коментар стосуються конкретно випадку, коли реалізація порожня або просто кидає виняток (як у оригінальному запитанні). Якщо реалізація повертає значення, схоже, Task.FromResultце краща відповідь. Втім, якщо ви які збираються кинути виняток, здається , ще одна відповідь прийшов в гру по приводу Task.FromExceptionстворення цього ніколи не ідеальне рішення. Чи погодились би ви?
BlueMonkMN

15

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

    BenchmarkDotNet=v0.10.11, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233537 Hz, Resolution=309.2589 ns, Timer=TSC
.NET Core SDK=2.1.2
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2600.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


         Method |  Job | Runtime |         Mean |       Error |      StdDev |       Median |          Min |          Max | Rank |  Gen 0 |  Gen 1 |  Gen 2 | Allocated |
--------------- |----- |-------- |-------------:|------------:|------------:|-------------:|-------------:|-------------:|-----:|-------:|-------:|-------:|----------:|
 CompletedAwait |  Clr |     Clr |    95.253 ns |   0.7491 ns |   0.6641 ns |    95.100 ns |    94.461 ns |    96.557 ns |    7 | 0.0075 |      - |      - |      24 B |
      Completed |  Clr |     Clr |    12.036 ns |   0.0659 ns |   0.0617 ns |    12.026 ns |    11.931 ns |    12.154 ns |    2 | 0.0076 |      - |      - |      24 B |
         Pragma |  Clr |     Clr |    87.868 ns |   0.3923 ns |   0.3670 ns |    87.789 ns |    87.336 ns |    88.683 ns |    6 | 0.0075 |      - |      - |      24 B |
     FromResult |  Clr |     Clr |   107.009 ns |   0.6671 ns |   0.6240 ns |   107.009 ns |   106.204 ns |   108.247 ns |    8 | 0.0584 |      - |      - |     184 B |
          Yield |  Clr |     Clr | 1,766.843 ns |  26.5216 ns |  24.8083 ns | 1,770.383 ns | 1,705.386 ns | 1,800.653 ns |    9 | 0.0877 | 0.0038 | 0.0019 |     320 B |
 CompletedAwait | Core |    Core |    37.201 ns |   0.1961 ns |   0.1739 ns |    37.227 ns |    36.970 ns |    37.559 ns |    4 | 0.0076 |      - |      - |      24 B |
      Completed | Core |    Core |     9.017 ns |   0.0690 ns |   0.0577 ns |     9.010 ns |     8.925 ns |     9.128 ns |    1 | 0.0076 |      - |      - |      24 B |
         Pragma | Core |    Core |    34.118 ns |   0.4576 ns |   0.4281 ns |    34.259 ns |    33.437 ns |    34.792 ns |    3 | 0.0076 |      - |      - |      24 B |
     FromResult | Core |    Core |    46.953 ns |   1.2728 ns |   1.1905 ns |    46.467 ns |    45.674 ns |    49.868 ns |    5 | 0.0533 |      - |      - |     168 B |
          Yield | Core |    Core | 2,480.980 ns | 199.4416 ns | 575.4347 ns | 2,291.978 ns | 1,810.644 ns | 4,085.196 ns |   10 | 0.0916 |      - |      - |     296 B |

Примітка: FromResult порівняння неможливо безпосередньо.

Код тесту:

   [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
   [ClrJob, CoreJob]
   [HtmlExporter, MarkdownExporter]
   [MemoryDiagnoser]
 public class BenchmarkAsyncNotAwaitInterface
 {
string context = "text context";
[Benchmark]
public int CompletedAwait()
{
    var t = new CompletedAwaitTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Completed()
{
    var t = new CompletedTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Pragma()
{
    var t = new PragmaTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

[Benchmark]
public int Yield()
{
    var t = new YieldTest();
    var a = t.DoAsync(context);
    a.Wait();
    return t.Length;
}

    [Benchmark]
    public int FromResult()
    {
        var t = new FromResultTest();
        var t2 = t.DoAsync(context);
        return t2.Result;
    }

public interface ITestInterface
{
    int Length { get; }
    Task DoAsync(string context);
}

class CompletedAwaitTest : ITestInterface
{
    public int Length { get; private set; }
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        await Task.CompletedTask;
    }
}

class CompletedTest : ITestInterface
{
    public int Length { get; private set; }
    public Task DoAsync(string context)
    {
        Length = context.Length;
        return Task.CompletedTask;
    }
}

class PragmaTest : ITestInterface
{
    public int Length { get; private set; }
    #pragma warning disable 1998
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        return;
    }
    #pragma warning restore 1998
}

class YieldTest : ITestInterface
{
    public int Length { get; private set; }
    public async Task DoAsync(string context)
    {
        Length = context.Length;
        await Task.Yield();
    }
}

    public interface ITestInterface2
    {
        Task<int> DoAsync(string context);
    }

    class FromResultTest : ITestInterface2
    {
        public async Task<int> DoAsync(string context)
        {
            var i = context.Length;
            return await Task.FromResult(i);
        }
    }

}


1
Прикро, що той, #pragmaздається, зазнає накладних витрат. Напевно, настільки ж накладні, як якщо б замість того, щоб повернути CompletedTaskвам створений і завершений AsyncOperation. Було б непогано мати можливість сказати компілятору, що це нормально пропустити, коли метод все одно працює синхронно.
бінкі

Чим, на вашу думку Task.CompletedTask, схожий Task.FromResult? Було б цікаво знати - я очікую, що FromResult був би найбільш аналогічним і все ще найкращим виконавцем, якщо вам доведеться повернути значення.
BlueMonkMN

Я додам його. Я думаю, що в цьому випадку код машини буде більш детальним, і CompletedTask переможе. Подивимось
Роман Покровський

1
Було б приємно побачити це оновлення для .NET Core 2.2, оскільки виділення в асинхронних машинах суттєво покращилися
Tseng

1
@Tseng Я запустив орієнтири на .NET Core 2.2.0. Очевидно, загальний час різний через різні апаратні засоби, але співвідношення залишається приблизно однаковим: Метод | .NET Core 2.0.3 Середній | .NET Core 2.2.0 Середнє завершено | 100% | 100% завершеноочікування | 412,57% | 377,22% від результату | 520,72% | 590,89% Прагма | 378,37% | 346,64% врожайність | 27514,47% | 23602,38%
Буря

10

Я знаю, що це стара нитка, і, можливо, це не матиме правильного ефекту для всіх звичаїв, але наступне наближається до того, що я можу просто кинути NotImplementedException, коли я ще не застосував метод, без зміни методу підпису. Якщо це проблематично, я би радий про це знати, але це для мене ледь не має значення: я використовую це лише в процесі розробки, так як його ефективність не все так важлива. І все-таки я радий почути, чому це погана ідея, якщо вона є.

public async Task<object> test()
{
    throw await new AwaitableNotImplementedException<object>();
}

Ось тип, який я додав, щоб зробити це можливим.

public class AwaitableNotImplementedException<TResult> : NotImplementedException
{
    public AwaitableNotImplementedException() { }

    public AwaitableNotImplementedException(string message) : base(message) { }

    // This method makes the constructor awaitable.
    public TaskAwaiter<AwaitableNotImplementedException<TResult>> GetAwaiter()
    {
        throw this;
    }
}

10

Так само, як оновлення відповіді Стівена, вам більше не потрібно писати TaskConstantsклас, оскільки існує новий допоміжний метод:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }

3
Не робіть цього. Слід стека не вказуватиме на ваш код. Виключення повинні бути кинуті, щоб повністю ініціалізуватися.
Даніель Б

1
Даніель Б - Так, ви абсолютно праві. Я змінив свою відповідь, щоб правильно кинути виняток.
Метт

3

Якщо ви вже зв’язалися з Реактивним розширенням, ви також можете зробити:

public async Task<object> NotImplemented()
{
    await Observable.Throw(new NotImplementedException(), null as object).ToTask();
}

public async Task<object> SimpleResult()
{
    await Observable.Return(myvalue).ToTask();
}

Реактивність та асинхронізація / очікування - самі по собі дивовижні, але вони також добре грають разом.

Необхідні включення:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;

3

Це може статися cs1998 нижче.

public async Task<object> Foo()
{
    return object;
}

Тоді ви можете реформувати нижче.

public async Task<object> Foo()
{
    var result = await Task.Run(() =>
    {
        return object;
    });
    return result;
}



1

Якщо у вас немає чого чекати, поверніться Task.FromResult

public Task<int> Success() // note: no "async"
{
  ... // Do not have await code
  var result = ...;
  return Task.FromResult(result);
}

1

Ось кілька варіантів залежно від підпису методу.

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

    public async Task<object> Test2()
    {
        return await Task.FromResult<object>(null);
    }

    public async Task<object> Test3()
    {
        return await Task.FromException<object>(new NotImplementedException());
    }

-1
// This is to get rid of warning CS1998, please remove when implementing this method.
await new Task(() => { }).ConfigureAwait(false);
throw new NotImplementedException();

-2

Ви можете скинути ключове слово async з методу та просто повернути його Завдання;

    public async Task DoTask()
    {
        State = TaskStates.InProgress;
        await RunTimer();
    }

    public Task RunTimer()
    {
        return new Task(new Action(() =>
        {
            using (var t = new time.Timer(RequiredTime.Milliseconds))
            {
                t.Elapsed += ((x, y) => State = TaskStates.Completed);
                t.Start();
            }
        }));
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.