Supprimer l'avertissement CS1998: cette méthode asynchrone n'a pas d'attente.

104

J'ai une interface avec des fonctions asynchrones. Certaines des classes qui implémentent l'interface n'ont rien à attendre, et certaines pourraient simplement lancer. C'est un peu ennuyeux avec tous les avertissements.

Lorsque vous n'utilisez pas await dans une fonction asynchrone.

Est-il possible de supprimer le message?

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

avertissement CS1998: Cette méthode asynchrone ne dispose pas d'opérateurs «d'attente» et s'exécutera de manière synchrone. Pensez à utiliser l'opérateur 'await' pour attendre les appels d'API non bloquants, ou 'await Task.Run (...)' pour effectuer un travail lié au processeur sur un thread d'arrière-plan.

Simon
la source
1
Lorsque vous n'utilisez pas le nouveau mot clé d'attente dans une fonction marquée comme asynchrone.
Simon
Que diriez-vous de nous montrer un exemple de code qui reproduit le problème?
John Saunders

Réponses:

107

J'ai une interface avec des fonctions asynchrones.

Les méthodes reviennent Task, je crois. asyncest un détail d'implémentation, il ne peut donc pas être appliqué aux méthodes d'interface.

Certaines des classes qui implémentent l'interface n'ont rien à attendre, et certaines pourraient simplement lancer.

Dans ces cas, vous pouvez profiter du fait qu'il asyncs'agit d'un détail d'implémentation.

Si vous avez rien à await, vous pouvez simplement revenir Task.FromResult:

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

Dans le cas du lancer NotImplementedException, la procédure est un peu plus verbeuse:

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

Si vous avez beaucoup de méthodes à lancer NotImplementedException(ce qui en soi peut indiquer qu'une refactorisation au niveau de la conception serait bonne), alors vous pouvez résumer la verbosité dans une classe d'assistance:

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 d'assistance réduit également les déchets que le GC devrait autrement collecter, car chaque méthode avec le même type de retour peut partager ses objets Tasket NotImplementedException.

J'ai plusieurs autres exemples de type "constante de tâche" dans ma bibliothèque AsyncEx .

Stephen Cleary
la source
1
Je n'ai pas pensé à perdre le mot-clé. Comme vous le dites, async n'a rien à voir avec l'interface. Mon mauvais, merci.
Simon
3
Pouvez-vous recommander une approche où le type de retour est juste Tâche (sans résultat?)
Mike
9
Avertissement: cette approche peut poser des problèmes car les erreurs ne se propagent pas comme prévu. Normalement, l'appelant s'attendra à ce qu'une exception dans votre méthode apparaisse dans le Task. Au lieu de cela, votre méthode lancera avant même d'avoir une chance de créer un fichier Task. Je pense vraiment que le meilleur modèle est de définir une asyncméthode sans awaitopérateur. Cela garantit que le code de la méthode est entièrement traité dans le cadre du Task.
Bob Meyers
11
Pour éviter CS1998, vous pouvez ajouter await Task.FromResult(0);à votre méthode. Cela ne devrait pas avoir d'impact significatif sur les performances (contrairement à Task.Yield ()).
Bob Meyers
3
@AndrewTheken: De nos jours, vous pouvez simplement faire return Task.CompletedTask;- le plus simple de tous.
Stephen Cleary
63

Une autre option, si vous voulez garder le corps de la fonction simple et ne pas écrire de code pour le supporter, est simplement de supprimer l'avertissement avec #pragma:

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

Si cela est assez courant, vous pouvez placer l'instruction disable en haut du fichier et omettre la restauration.

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

Jamie Macia
la source
41

Une autre façon de conserver le mot-clé async (au cas où vous voudriez le conserver) est d'utiliser:

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

Une fois que vous remplissez la méthode, vous pouvez simplement supprimer l'instruction. J'utilise beaucoup cela, surtout lorsqu'une méthode peut attendre quelque chose, mais pas toutes les implémentations.

Simon Mattes
la source
Cela devrait être la réponse acceptée. Parfois, les implémentations d'interface n'ont pas besoin d'être asynchrones, c'est beaucoup plus propre que de tout encapsuler dans un Task.Runappel.
Andrew Theken
12
attendre Task.CompletedTask; // pourrait être une meilleure option
Frode Nilsen
@FrodeNilsen pour une raison quelconque Task.CompletedTaskne semble plus exister.
Sebastián Vansteenkiste
1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, .Net Core 1.0->
Frode Nilsen
1
@AndrewTheken Il m'a fallu un certain temps pour arriver à la conclusion que cette réponse et votre commentaire s'appliquent spécifiquement au cas où l'implémentation est vide ou lève simplement une exception (comme dans la question d'origine). Si une implémentation renvoie une valeur, il semble que Task.FromResultc'est la meilleure réponse. Pour cette question, si vous êtes allez jeter une exception, il semble une autre réponse est entré en jeu en ce qui concerne Task.FromExceptionce qui en fait jamais la solution idéale. Accepteriez-vous?
BlueMonkMN
15

Il y a une différence entre les solutions et à proprement parler, vous devez savoir comment l'appelant va appeler la méthode async, mais avec le modèle d'utilisation par défaut qui suppose ".Wait ()" sur le résultat de la méthode - " return Task.CompletedTask " est la meilleure solution.

    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 |

Remarque: FromResultne peut pas être comparé directement.

Code de test:

   [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
la source
1
Il est malheureux que celui- #pragmaci semble entraîner des frais généraux. Probablement autant de frais généraux que si, au lieu de renvoyer, CompletedTaskvous avez créé et complété un fichier AsyncOperation. Ce serait bien de pouvoir dire au compilateur qu'il est correct de l'ignorer lorsque la méthode s'exécute de toute façon de manière synchrone.
binki
À quel point pensez-vous Task.CompletedTaskest-il similaire Task.FromResult? Il serait intéressant de savoir - je m'attends à ce que FromResult soit le plus analogue et toujours le plus performant si vous devez renvoyer une valeur.
BlueMonkMN
Je vais l'ajouter. Je pense que le code de la machine d'état sera plus verbeux dans ce cas et CompletedTask gagnera. Voyons voir
Roman Pokrovskij
1
Ce serait bien de voir cette mise à jour pour .NET Core 2.2, car les allocations dans les machines à états asynchrones ont été considérablement améliorées
Tseng
1
@Tseng J'ai exécuté les benchmarks sur .NET Core 2.2.0. De toute évidence, le temps total est différent en raison du matériel différent, mais le rapport reste à peu près le même: Méthode | .NET Core 2.0.3 Mean | .NET Core 2.2.0 moyen terminé | 100% | 100% terminéAttendre | 412,57% | 377,22% Du résultat | 520,72% | 590,89% Pragma | 378,37% | Rendement de 346,64% | 27514,47% | 23602.38%
Storm
10

Je sais que c'est un ancien fil, et peut-être que cela n'aura pas le bon effet pour toutes les utilisations, mais ce qui suit est aussi proche que possible de pouvoir simplement lancer une NotImplementedException lorsque je n'ai pas encore implémenté de méthode, sans modifier la signature de la méthode. Si c'est problématique, je serais heureux de le savoir, mais cela m'importe peu: je ne l'utilise que pendant le développement de toute façon, donc sa performance n'est pas si importante. Pourtant, je serais heureux d'apprendre pourquoi c'est une mauvaise idée, si c'est le cas.

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

Voici le type que j'ai ajouté pour rendre cela possible.

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
la source
10

Tout comme une mise à jour de la réponse de Stephen, vous n'avez plus besoin d'écrire la TaskConstantsclasse car il existe une nouvelle méthode d'assistance:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
Mat
la source
3
Ne fais pas ça. La trace de pile ne pointera pas sur votre code. Des exceptions doivent être levées pour être complètement initialisées.
Daniel B
1
Daniel B - Oui, vous avez absolument raison. J'ai modifié ma réponse pour lancer correctement l'exception.
Matt
3

Si vous êtes déjà lié à Reactive Extension, vous pouvez également faire:

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 et async / await sont tous deux incroyables en eux-mêmes, mais ils jouent également bien ensemble.

Les éléments nécessaires sont:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
John
la source
3

Cela pourrait se produire cs1998 ci-dessous.

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

Ensuite, vous pouvez réformer ci-dessous.

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

Vous pouvez essayer ceci:

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

Si vous n'avez rien à attendre, renvoyez Task.FromResult

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

Voici quelques alternatives en fonction de la signature de votre méthode.

    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
la source
-1
// This is to get rid of warning CS1998, please remove when implementing this method.
await new Task(() => { }).ConfigureAwait(false);
throw new NotImplementedException();
bkoelman
la source
-2

Vous pouvez supprimer le mot-clé async de la méthode et lui demander simplement de renvoyer 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
la source