Подавить предупреждение 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;
    }
}

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



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 из метода и просто вернуть 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();
            }
        }));
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.