Sopprimi avviso CS1998: questo metodo asincrono manca di "await"


104

Ho un'interfaccia con alcune funzioni asincrone. Alcune delle classi che implementano l'interfaccia non hanno nulla da attendere e alcune potrebbero semplicemente lanciare. È un po 'fastidioso con tutti gli avvertimenti.

Quando non si utilizza await in una funzione async.

È possibile sopprimere il messaggio?

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

avviso CS1998: questo metodo asincrono non dispone di operatori "await" e verrà eseguito in modo sincrono. Prendi in considerazione l'utilizzo dell'operatore "await" per attendere le chiamate API non bloccanti o "await Task.Run (...)" per eseguire il lavoro associato alla CPU su un thread in background.


1
Quando non si utilizza la nuova parola chiave await in una funzione contrassegnata come async.
Simon

Che ne dici di mostrarci un esempio di codice che riproduce il problema?
John Saunders

Risposte:


106

Ho un'interfaccia con alcune funzioni asincrone.

Metodi di ritorno Task, credo. asyncè un dettaglio di implementazione, quindi non può essere applicato ai metodi di interfaccia.

Alcune delle classi che implementano l'interfaccia non hanno nulla da attendere e alcune potrebbero semplicemente lanciare.

In questi casi, puoi trarre vantaggio dal fatto che asyncè un dettaglio di implementazione.

Se non hai nulla da fare await, puoi semplicemente tornare Task.FromResult:

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

In caso di lancio NotImplementedException, la procedura è un po 'più prolissa:

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

Se hai molti metodi che lanciano NotImplementedException(che a sua volta potrebbe indicare che un po 'di refactoring a livello di progettazione sarebbe buono), allora potresti racchiudere la verbosità in una classe helper:

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 classe helper riduce anche i rifiuti che altrimenti il ​​GC dovrebbe raccogliere, poiché ogni metodo con lo stesso tipo restituito può condividere i propri oggetti Taske NotImplementedException.

Ho molti altri esempi di tipo "costante di attività" nella mia libreria AsyncEx .


1
Non pensavo di perdere la parola chiave. Come dici tu, async non ha nulla a che fare con l'interfaccia. Colpa mia, grazie.
Simon

3
Puoi consigliare un approccio in cui il tipo restituito è solo Attività (senza risultato?)
Mike

9
Avviso: questo approccio può causare problemi perché gli errori non verranno propagati nel modo previsto. Normalmente il chiamante si aspetta che un'eccezione nel tuo metodo venga visualizzata all'interno del file Task. Invece, il tuo metodo verrà lanciato prima ancora che abbia la possibilità di creare un file Task. Penso davvero che il modello migliore sia definire un asyncmetodo senza awaitoperatori. Ciò garantisce che il codice all'interno del metodo venga trattato come parte di Task.
Bob Meyers

11
Per evitare CS1998, puoi aggiungere await Task.FromResult(0);al tuo metodo. Ciò non dovrebbe avere alcun impatto significativo sulle prestazioni (a differenza di Task.Yield ()).
Bob Meyers

3
@AndrewTheken: In questi giorni puoi semplicemente fare return Task.CompletedTask;- il più semplice di tutti.
Stephen Cleary

63

Un'altra opzione, se vuoi mantenere semplice il corpo della funzione e non scrivere codice per supportarla, è semplicemente sopprimere l'avviso con #pragma:

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

Se questo è abbastanza comune, è possibile inserire l'istruzione disable all'inizio del file e omettere il ripristino.

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


40

Un altro modo per preservare la parola chiave asincrona (nel caso in cui desideri mantenerla) è usare:

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

Dopo aver popolato il metodo, puoi semplicemente rimuovere l'istruzione. Lo uso molto soprattutto quando un metodo potrebbe attendere qualcosa ma non tutte le implementazioni lo fanno effettivamente.


Questa dovrebbe essere la risposta accettata. A volte non è necessario che le implementazioni dell'interfaccia siano asincrone, è molto più semplice che racchiudere tutto in una Task.Runchiamata.
Andrew Theken

12
wait Task.CompletedTask; // potrebbe essere un'opzione migliore
Frode Nilsen

@FrodeNilsen per qualche motivo Task.CompletedTasksembra non esistere più.
Sebastián Vansteenkiste

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

1
@AndrewTheken Mi ci è voluto un po 'per giungere alla conclusione che questa risposta e il tuo commento si applicano specificamente al caso in cui l'implementazione è vuota o genera semplicemente un'eccezione (come nella domanda originale). Se un'implementazione restituisce un valore, sembra che Task.FromResultsia la risposta migliore. Del resto, se si sta andando a un'eccezione, sembra un'altra risposta è entrare in gioco per quanto riguarda Task.FromExceptionrendendo questo non è mai la soluzione ideale. Saresti d'accordo?
BlueMonkMN

15

C'è differenza tra le soluzioni e in senso stretto dovresti sapere come il chiamante chiamerà il metodo asincrono, ma con il modello di utilizzo predefinito che presuppone ".Wait ()" sul risultato del metodo - " return Task.CompletedTask " è la soluzione migliore.

    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: FromResultnon può essere confrontato direttamente.

Codice di prova:

   [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
È un peccato che #pragmaquello sembri incorrere in spese generali. Probabilmente lo stesso overhead come se invece di tornare CompletedTaskavessi creato e completato un file AsyncOperation. Sarebbe bello poter dire al compilatore che va bene saltarlo quando il metodo viene comunque eseguito in modo sincrono.
binki

Quanto simile pensi Task.CompletedTasksia simile a Task.FromResult? Sarebbe interessante saperlo: mi aspetto che FromResult sarebbe il più analogo e comunque il migliore interprete se devi restituire un valore.
BlueMonkMN

Lo aggiungerò. Penso che il codice della macchina a stati sarà più dettagliato in questo caso e CompletedTask vincerà. Vediamo
Roman Pokrovskij

1
Sarebbe bello vedere questo aggiornato per .NET Core 2.2, poiché le allocazioni nelle macchine a stati asincrone sono state drasticamente migliorate
Tseng

1
@Tseng ho eseguito i benchmark su .NET Core 2.2.0. Ovviamente, il tempo totale è diverso a causa del diverso hardware, ma il rapporto rimane più o meno lo stesso: Metodo | .NET Core 2.0.3 Media | .NET Core 2.2.0 Media completato | 100% | 100% completatoAspetta | 412,57% | 377,22% dal risultato | 520,72% | 590,89% Pragma | 378,37% | 346,64% di rendimento | 27514,47% | 23602.38%
Tempesta il

10

So che questo è un vecchio thread e forse questo non avrà l'effetto giusto per tutti gli usi, ma quanto segue è il più vicino possibile alla possibilità di lanciare semplicemente un'eccezione NotImplementedException quando non ho ancora implementato un metodo, senza alterare la firma del metodo. Se è problematico sarei felice di saperlo, ma per me non importa: lo uso solo durante lo sviluppo comunque, quindi come si comporta non è poi così importante. Tuttavia, sarei felice di sapere perché è una cattiva idea, se lo è.

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

Ecco il tipo che ho aggiunto per renderlo possibile.

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

Proprio come un aggiornamento alla risposta di Stephen, non è più necessario scrivere la TaskConstantsclasse poiché esiste un nuovo metodo di supporto:

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

3
Non farlo. L'analisi dello stack non punterà al codice. Le eccezioni devono essere generate per essere completamente inizializzate.
Daniel B

1
Daniel B - Sì, hai assolutamente ragione. Ho modificato la mia risposta per lanciare correttamente l'eccezione.
Matt

3

Nel caso in cui ti colleghi già a Reactive Extension, puoi anche fare:

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

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

Reattivo e asincrono / attendono sono entrambi fantastici in sé e per sé, ma giocano anche bene insieme.

Include necessari sono:

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

3

Potrebbe essere cs1998 di seguito.

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

Quindi puoi riformare di seguito.

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

3

Potresti provare questo:

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


1

Se non hai nulla da attendere, restituisci Task.FromResult

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

1

Ecco alcune alternative a seconda della firma del metodo.

    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

È possibile eliminare la parola chiave asincrona dal metodo e fare in modo che restituisca 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();
            }
        }));
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.