Подавить предупреждение CS1998: в этом асинхронном методе отсутствует ожидание

104

У меня есть интерфейс с некоторыми асинхронными функциями. Некоторым классам, реализующим интерфейс, нечего ждать, а некоторые могут просто выбросить. Все предупреждения немного раздражают.

Если не использовать ожидание в асинхронной функции.

Можно ли подавить сообщение?

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

предупреждение CS1998: в этом асинхронном методе отсутствуют операторы ожидания, и он будет выполняться синхронно. Рассмотрите возможность использования оператора await для ожидания неблокирующих вызовов API или await Task.Run (...) для выполнения работы, связанной с процессором, в фоновом потоке.

Саймон
источник
1
Когда не используется новое ключевое слово await в функции, помеченной как async.
Саймон
Как насчет того, чтобы показать нам образец кода, который воспроизводит проблему?
Джон Сондерс

Ответы:

107

У меня есть интерфейс с некоторыми асинхронными функциями.

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
Можете ли вы порекомендовать подход, в котором тип возвращаемого значения - это просто задача (без результата?)
Майк,
10
Предупреждение: этот подход может вызвать проблемы, потому что ошибки не будут распространяться так, как вы ожидаете. Обычно вызывающая сторона ожидает появления исключения в вашем методе внутри Task. Вместо этого ваш метод выбросит еще до того, как у него появится возможность создать Task. Я действительно думаю, что лучший шаблон - определить asyncметод без awaitоператоров. Это гарантирует, что весь код внутри метода будет рассматриваться как часть Task.
Боб Мейерс
11
Чтобы избежать CS1998, вы можете добавить await Task.FromResult(0);в свой метод. Это не должно иметь значительного влияния на производительность (в отличие от Task.Yield ()).
Боб Мейерс
3
@AndrewTheken: В наши дни вы можете просто делать return Task.CompletedTask;- самое простое из всех.
Стивен Клири
65

Другой вариант, если вы хотите сохранить простоту тела функции и не писать код для ее поддержки, - просто подавить предупреждение с помощью #pragma:

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

Если это достаточно часто, вы можете поместить инструкцию disable в верхней части файла и пропустить восстановление.

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

Джейми Мэйсиа
источник
43

Другой способ сохранить ключевое слово async (если вы хотите его сохранить) - использовать:

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

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

Саймон Мэттес
источник
Это должен быть принятый ответ. Иногда реализации интерфейса не обязательно должны быть асинхронными, это намного чище, чем обертывание всего в Task.Runвызове.
Эндрю Текен
12
ждите Task.CompletedTask; // может быть лучшим вариантом
Frode 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. Было бы неплохо иметь возможность сказать компилятору, что можно пропустить это, когда метод в любом случае работает синхронно.
binki
Насколько похоже Task.CompletedTaskпохоже на Task.FromResult? Было бы интересно узнать - я ожидаю, что FromResult будет наиболее аналогичным и по-прежнему лучшим исполнителем, если вам нужно вернуть значение.
BlueMonkMN
Я его добавлю. Я думаю, что в этом случае код конечного автомата будет более подробным, и CompletedTask победит. Посмотрим
Роман Покровский
1
Было бы неплохо увидеть это обновление для .NET Core 2.2, поскольку выделения в асинхронных
Ценг
1
@Tseng Я провел тесты на .NET Core 2.2.0. Очевидно, что общее время отличается из-за разного оборудования, но соотношение остается примерно тем же: Метод | .NET Core 2.0.3 Среднее | .NET Core 2.2.0 Среднее завершено | 100% | 100% ЗавершеноЖдать | 412,57% | 377,22% FromResult | 520,72% | 590,89% Pragma | 378,37% | 346,64% Доходность | 27514,47% | 23602.38%
Storm
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;
    }
}
rrreee
источник
10

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

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
Мэтт
источник
3
Не делай этого. Трассировка стека не будет указывать на ваш код. Для полной инициализации должны быть созданы исключения.
Daniel B
1
Даниэль Б. - Да, вы абсолютно правы. Я изменил свой ответ, чтобы правильно выбросить исключение.
Мэтт
3

Если вы уже ссылаетесь на Reactive Extension, вы также можете:

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

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

Reactive и async / await великолепны сами по себе, но они также хорошо работают вместе.

В комплект входят:

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;
}
瀧 谷 賢 司
источник
3

Вы можете попробовать это:

public async Task<object> test()
{
await Task.CompletedTask; 
}
Фейсал Мехбуб
источник
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());
    }
Ludde
источник
-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 из метода и просто вернуть Task;

    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();
            }
        }));
    }
Ракз
источник