Suprima la advertencia CS1998: este método asincrónico carece de 'espera'

104

Tengo una interfaz con algunas funciones asincrónicas. Algunas de las clases que implementan la interfaz no tienen nada que esperar, y algunas pueden simplemente lanzar. Es un poco molesto con todas las advertencias.

Cuando no se usa await en una función asincrónica.

¿Es posible suprimir el mensaje?

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

advertencia CS1998: este método asincrónico carece de operadores 'en espera' y se ejecutará de forma sincrónica. Considere usar el operador 'await' para esperar las llamadas API sin bloqueo, o 'await Task.Run (...)' para hacer un trabajo vinculado a la CPU en un hilo en segundo plano.

Simón
fuente
1
Cuando no se usa la nueva palabra clave await en una función marcada como asíncrona.
Simon
¿Qué tal si nos muestra una muestra de código que reproduzca el problema?
John Saunders

Respuestas:

106

Tengo una interfaz con algunas funciones asincrónicas.

Los métodos regresan Task, creo. asynces un detalle de implementación, por lo que no se puede aplicar a métodos de interfaz.

Algunas de las clases que implementan la interfaz no tienen nada que esperar, y algunas pueden simplemente lanzar.

En estos casos, puede aprovechar el hecho de que asynces un detalle de implementación.

Si no tiene nada que hacer await, puede simplemente regresar Task.FromResult:

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

En el caso de lanzar NotImplementedException, el procedimiento es un poco más prolijo:

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

Si tiene muchos métodos lanzando NotImplementedException(lo que en sí mismo puede indicar que una refactorización a nivel de diseño sería buena), entonces podría resumir la palabra en una clase 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;
}

La clase auxiliar también reduce la basura que el GC tendría que recolectar, ya que cada método con el mismo tipo de retorno puede compartir sus objetos Tasky NotImplementedException.

Tengo varios otros ejemplos de tipo "constante de tarea" en mi biblioteca AsyncEx .

Stephen Cleary
fuente
1
No pensé en perder la palabra clave. Como dices, async no tiene nada que ver con la interfaz. Mi mal, gracias.
Simon
3
¿Puede recomendar un enfoque donde el tipo de retorno sea solo Tarea (sin un resultado?)
Mike
9
Advertencia: este enfoque puede causar problemas porque los errores no se propagarán de la forma esperada. Normalmente, la persona que llama esperará que aparezca una excepción en su método dentro de Task. En cambio, su método arrojará antes de que tenga la oportunidad de crear un Task. Realmente creo que el mejor patrón es definir un asyncmétodo sin awaitoperadores. Esto asegura que el código dentro del método sea tratado como parte del Task.
Bob Meyers
11
Para evitar CS1998, puede agregar await Task.FromResult(0);a su método. Esto no debería tener ningún impacto significativo en el rendimiento (a diferencia de Task.Yield ()).
Bob Meyers
3
@AndrewTheken: En estos días puedes hacerlo return Task.CompletedTask;, el más simple de todos.
Stephen Cleary
63

Otra opción, si desea mantener el cuerpo de la función simple y no escribir código para admitirlo, es simplemente suprimir la advertencia con #pragma:

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

Si esto es lo suficientemente común, puede colocar la declaración de desactivación en la parte superior del archivo y omitir la restauración.

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

Jamie Macia
fuente
40

Otra forma de preservar la palabra clave async (en caso de que quiera mantenerla) es usar:

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

Una vez que complete el método, simplemente puede eliminar la declaración. Lo uso mucho, especialmente cuando un método puede esperar algo, pero no todas las implementaciones lo hacen.

Simon Mattes
fuente
Esta debería ser la respuesta aceptada. A veces, las implementaciones de la interfaz no necesitan ser asincrónicas, esto es mucho más limpio que envolver todo en una Task.Runllamada.
Andrew Theken
12
espera Task.CompletedTask; // podría ser una mejor opción
Frode Nilsen
@FrodeNilsen por alguna razón Task.CompletedTaskparece que ya no existe.
Sebastián Vansteenkiste
1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, .Net Core 1.0->
Frode Nilsen
1
@AndrewTheken Me tomó un tiempo llegar a la conclusión de que esta respuesta y su comentario se aplican específicamente al caso en el que la implementación está vacía o simplemente arroja una excepción (como en la pregunta original). Si una implementación devuelve un valor, parece que Task.FromResultes la mejor respuesta. Por lo demás, si se va a lanzar una excepción, parece otra respuesta ha entrado en juego con respecto a Task.FromExceptionhacer esto nunca la solución ideal. ¿Estarías de acuerdo?
BlueMonkMN
15

Hay una diferencia entre las soluciones y, estrictamente hablando, debe saber cómo la persona que llama va a llamar al método asíncrono, pero con el patrón de uso predeterminado que asume ".Wait ()" en el resultado del método - " return Task.CompletedTask " es la mejor solución.

    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: FromResultno se puede comparar directamente.

Código de prueba:

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

}

Roman Pokrovskij
fuente
1
Es lamentable que #pragmaparezca incurrir en gastos generales. Probablemente tanta sobrecarga como si en lugar de devolver hubiera CompletedTaskcreado y completado un AsyncOperation. Sería bueno poder decirle al compilador que está bien omitir eso cuando el método se ejecuta sincrónicamente de todos modos.
binki
¿Cuán similar crees que Task.CompletedTaskes similar Task.FromResult? Sería interesante saberlo: espero que FromResult sea el más análogo y siga siendo el de mejor rendimiento si tiene que devolver un valor.
BlueMonkMN
Lo agregaré. Creo que el código de la máquina de estado será más detallado en este caso y CompletedTask ganará. Veamos
Roman Pokrovskij
1
Sería bueno ver esto actualizado para .NET Core 2.2, ya que las asignaciones en las máquinas de estado asíncronas se han mejorado drásticamente
Tseng
1
@Tseng He ejecutado los puntos de referencia en .NET Core 2.2.0. Obviamente, el tiempo total es diferente debido al hardware diferente, pero la proporción sigue siendo aproximadamente la misma: Método | .NET Core 2.0.3 Media | .NET Core 2.2.0 Media completada | 100% | 100% CompletedAwait | 412,57% | 377,22% FromResult | 520,72% | 590,89% Pragma | 378,37% | Rendimiento del 346,64% | 27514,47% | 23602.38%
Tormenta
10

Sé que este es un hilo antiguo, y tal vez esto no tenga el efecto correcto para todos los usos, pero lo siguiente es lo más cerca que puedo llegar a poder simplemente lanzar una NotImplementedException cuando aún no he implementado un método, sin alterar la firma del método. Si es problemático, estaría feliz de saberlo, pero apenas me importa: de todos modos, solo uso esto mientras estoy en desarrollo, por lo que su rendimiento no es tan importante. Aún así, estaría feliz de saber por qué es una mala idea, si lo es.

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

Aquí está el tipo que agregué para hacerlo posible.

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
fuente
10

Solo como una actualización de Stephen's Answer, ya no necesita escribir la TaskConstantsclase ya que hay un nuevo método de ayuda:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
Mate
fuente
3
No hagas esto. El seguimiento de la pila no apuntará a su código. Deben lanzarse excepciones para que se inicialicen por completo.
Daniel B
1
Daniel B - Sí, tienes toda la razón. He modificado mi respuesta para lanzar correctamente la excepción.
Matt
3

En caso de que ya se vincule con Reactive Extension, también puede hacer:

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 y async / await son increíbles por sí mismos, pero también funcionan bien juntos.

Los incluidos necesarios son:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
Juan
fuente
3

Podría ocurrir cs1998 a continuación.

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

Entonces puedes reformar a continuación.

public async Task<object> Foo()
{
    var result = await Task.Run(() =>
    {
        return object;
    });
    return result;
}
瀧 谷 賢 司
fuente
3

Puede intentar esto:

public async Task<object> test()
{
await Task.CompletedTask; 
}
Faisal Mehboob
fuente
1

Si no tiene nada que esperar, devuelva Task.FromResult

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

Aquí hay algunas alternativas dependiendo de la firma de su 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());
    }
Ludde
fuente
-1
// This is to get rid of warning CS1998, please remove when implementing this method.
await new Task(() => { }).ConfigureAwait(false);
throw new NotImplementedException();
bkoelman
fuente
-2

Puede eliminar la palabra clave async del método y hacer que devuelva 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();
            }
        }));
    }
Rakz
fuente