Suprimir aviso CS1998: este método assíncrono não tem 'espera'


104

Eu tenho uma interface com algumas funções assíncronas. Algumas das classes que implementam a interface não têm nada a esperar, e algumas podem apenas lançar. É um pouco chato com todos os avisos.

Quando não estiver usando o await em uma função assíncrona.

É possível suprimir a mensagem?

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

aviso CS1998: Este método assíncrono não tem operadores 'em espera' e será executado de forma síncrona. Considere usar o operador 'await' para aguardar chamadas de API sem bloqueio ou 'await Task.Run (...)' para fazer trabalho vinculado à CPU em um thread de segundo plano.


1
Quando não estiver usando a nova palavra-chave await em uma função marcada como assíncrona.
Simon

Que tal nos mostrar um exemplo de código que reproduz o problema?
John Saunders

Respostas:


106

Eu tenho uma interface com algumas funções assíncronas.

Métodos retornando Task, eu acredito. asyncé um detalhe de implementação, portanto, não pode ser aplicado a métodos de interface.

Algumas das classes que implementam a interface não têm nada a esperar, e algumas podem apenas lançar.

Nesses casos, você pode tirar vantagem do fato de que asyncé um detalhe de implementação.

Se você não tiver nada para fazer await, pode simplesmente retornar Task.FromResult:

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

No caso de arremesso NotImplementedException, o procedimento é um pouco mais prolixo:

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

Se você tiver muitos métodos de lançamento NotImplementedException(o que por si só pode indicar que alguma refatoração no nível de design seria boa), você poderia resumir o prolixo em uma classe auxiliar:

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

A classe auxiliar também reduz o lixo que o GC teria que coletar, uma vez que cada método com o mesmo tipo de retorno pode compartilhar seus objetos Taske NotImplementedException.

Tenho vários outros exemplos de tipo de "constante de tarefa" em minha biblioteca AsyncEx .


1
Não pensei em perder a palavra-chave. Como você disse, async não tem nada a ver com a interface. Meu mal, obrigado.
Simon

3
Você pode recomendar uma abordagem em que o tipo de retorno seja apenas Tarefa (sem um resultado?)
Mike

9
Aviso: essa abordagem pode causar problemas porque os erros não serão propagados da maneira esperada. Normalmente, o chamador espera que uma exceção em seu método apareça dentro do Task. Em vez disso, seu método será lançado antes mesmo de ter a chance de criar um Task. Eu realmente acho que o melhor padrão é definir um asyncmétodo sem awaitoperadores. Isso garante que o código dentro do método seja tratado como parte do Task.
Bob Meyers

11
Para evitar CS1998, você pode adicionar await Task.FromResult(0);ao seu método. Isso não deve ter nenhum impacto significativo no desempenho (ao contrário de Task.Yield ()).
Bob Meyers

3
@AndrewTheken: Hoje em dia você pode apenas fazer return Task.CompletedTask;- o mais simples de todos.
Stephen Cleary

63

Outra opção, se você deseja manter o corpo da função simples e não escrever código para suportá-lo, é simplesmente suprimir o aviso com # pragma:

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

Se isso for bastante comum, você pode colocar a instrução de desativação no início do arquivo e omitir a restauração.

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


40

Outra maneira de preservar a palavra-chave assíncrona (caso você queira mantê-la) é usar:

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

Depois de preencher o método, você pode simplesmente remover a instrução. Eu uso muito isso, especialmente quando um método pode esperar algo, mas nem toda implementação realmente o faz.


Esta deve ser a resposta aceita. Às vezes, as implementações de interface não precisam ser assíncronas, isso é muito mais limpo do que envolver tudo em uma Task.Runchamada.
Andrew Theken

12
esperar Task.CompletedTask; // pode ser uma opção melhor
Frode Nilsen,

@FrodeNilsen por algum motivo Task.CompletedTaskparece não existir mais.
Sebastián Vansteenkiste

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

1
@AndrewTheken Levei um tempo para chegar à conclusão de que esta resposta e seu comentário se aplicam especificamente ao caso em que a implementação está vazia ou apenas lança uma exceção (como na pergunta original). Se uma implementação retornar um valor, parece que Task.FromResulté a melhor resposta. Para essa matéria, se você estiver indo para lançar uma exceção, parece outra resposta veio em jogo a respeito Task.FromExceptionde fazer isso nunca a solução ideal. Você concordaria?
BlueMonkMN de

15

Há uma diferença entre as soluções e, estritamente falando, você deve saber como o chamador vai chamar o método assíncrono, mas com o padrão de uso padrão que assume ".Wait ()" no resultado do método - " return Task.CompletedTask " é a melhor solução.

    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 |

Nota: FromResultnão pode ser comparado diretamente.

Código de teste:

   [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
É lamentável que #pragmaum pareça incorrer em sobrecarga. Provavelmente tanto overhead como se, em vez de retornar, CompletedTaskvocê tivesse criado e completado um AsyncOperation. Seria bom poder dizer ao compilador que não há problema em pular isso quando o método for executado de maneira síncrona.
binki

Como você acha que Task.CompletedTaské semelhante a Task.FromResult? Seria interessante saber - espero que FromResult seja mais análogo e ainda tenha o melhor desempenho se você precisar retornar um valor.
BlueMonkMN

Eu vou adicionar. Acho que o código de máquina do estado será mais detalhado neste caso e a CompletedTask vencerá. Vejamos
Roman Pokrovskij

1
Seria bom ver isso atualizado para .NET Core 2.2, já que as alocações nas máquinas de estado assíncronas foram drasticamente aprimoradas
Tseng

1
@Tseng Eu executei benchmarks no .NET Core 2.2.0. Obviamente, o tempo total é diferente devido a hardware diferente, mas a proporção permanece praticamente a mesma: Método | Média do .NET Core 2.0.3 | .NET Core 2.2.0 Média Concluída | 100% | 100% CompletedAwait | 412,57% | 377,22% FromResult | 520,72% | 590,89% Pragma | 378,37% | 346,64% de rendimento | 27514,47% | 23602,38%
Tempestade

10

Eu sei que este é um tópico antigo e talvez não tenha o efeito certo para todos os usos, mas o que se segue é o mais próximo que posso chegar de simplesmente lançar um NotImplementedException quando ainda não implementei um método, sem alterar a assinatura do método. Se for problemático, ficaria feliz em saber sobre isso, mas pouco importa para mim: eu só uso isso durante o desenvolvimento de qualquer maneira, então o seu desempenho não é tão importante. Ainda assim, ficaria feliz em saber por que é uma má ideia, se for.

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

Aqui está o tipo que adicionei para tornar isso possível.

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

Apenas para atualizar a resposta de Stephen, você não precisa mais escrever a TaskConstantsclasse, pois há um novo método auxiliar:

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

3
Não faça isso. O rastreamento de pilha não apontará para o seu código. As exceções devem ser lançadas para serem completamente inicializadas.
Daniel B

1
Daniel B - Sim, você está absolutamente certo. Modifiquei minha resposta para lançar a exceção corretamente.
Matt

3

Caso você já se vincule à extensão reativa, também pode fazer:

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 e async / await são incríveis por si só, mas também funcionam bem juntos.

Os itens necessários são:

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

3

Pode ser ocorrido cs1998 abaixo.

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

Então você pode reformar abaixo.

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

3

Você pode tentar isto:

public async Task<object> test()
{
await Task.CompletedTask; 
}


1

Se você não tem nada a esperar, retorne Task.FromResult

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

1

Aqui estão algumas alternativas, dependendo da assinatura do seu método.

    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

Você pode eliminar a palavra-chave async do método e apenas fazer com que ela retorne 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();
            }
        }));
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.