Pomiń ostrzeżenie CS1998: w tej metodzie asynchronicznej brakuje opcji „czekaj”

104

Mam interfejs z kilkoma funkcjami asynchronicznymi. Niektóre klasy implementujące interfejs nie mają na co czekać, a niektóre mogą po prostu wyrzucić. To trochę denerwujące ze wszystkimi ostrzeżeniami.

Gdy nie używasz await w funkcji asynchronicznej.

Czy można ukryć przekaz?

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

ostrzeżenie CS1998: Ta metoda asynchroniczna nie zawiera operatorów „await” i będzie działać synchronicznie. Rozważ użycie operatora „await”, aby oczekiwać nieblokujących wywołań API, lub „await Task.Run (...)”, aby wykonać pracę związaną z procesorem w wątku w tle.

Szymon
źródło
1
Gdy nie używasz nowego słowa kluczowego await w funkcji oznaczonej jako async.
Simon
Co powiesz na pokazanie nam próbki kodu, która odtwarza problem?
John Saunders,

Odpowiedzi:

107

Mam interfejs z kilkoma funkcjami asynchronicznymi.

TaskMyślę, że metody wracają . asyncto szczegół implementacji, więc nie można go zastosować do metod interfejsu.

Niektóre klasy implementujące interfejs nie mają na co czekać, a niektóre mogą po prostu wyrzucić.

W takich przypadkach można wykorzystać fakt, że asyncjest to szczegół implementacji.

Jeśli nie masz nic do awaitroboty, możesz po prostu wrócić Task.FromResult:

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

W przypadku rzucania NotImplementedExceptionprocedura jest nieco bardziej rozwlekła:

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

Jeśli masz wiele metod do rzucania NotImplementedException(co samo w sobie może wskazywać, że niektóre refaktoryzacje na poziomie projektu byłyby dobre), możesz zawrzeć te słowa w klasie pomocniczej:

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

Klasa pomocnicza zmniejsza również ilość śmieci, które w przeciwnym razie musiałaby zbierać GC, ponieważ każda metoda z tym samym zwracanym typem może współużytkować swoje obiekty Taski NotImplementedException.

Mam kilka innych przykładów typu „stałej zadania” w mojej bibliotece AsyncEx .

Stephen Cleary
źródło
1
Nie myślałem o utracie słowa kluczowego. Jak mówisz, asynchronizacja nie ma nic wspólnego z interfejsem. Mój błąd, dziękuję.
Simon
3
Czy możesz polecić podejście, w którym typem zwracanym jest po prostu Task (bez wyniku?)
Mike,
10
Ostrzeżenie: takie podejście może powodować problemy, ponieważ błędy nie będą propagowane w oczekiwany sposób. Zwykle obiekt wywołujący będzie oczekiwał, że wyjątek w metodzie zostanie wyświetlony w ramach Task. Zamiast tego Twoja metoda wyrzuci, zanim jeszcze będzie miała szansę utworzyć plik Task. Naprawdę uważam, że najlepszym wzorcem jest zdefiniowanie asyncmetody bez awaitoperatorów. Gwarantuje to, że kod w metodzie zostanie potraktowany jako część Task.
Bob Meyers,
11
Aby uniknąć CS1998, możesz dodać await Task.FromResult(0);do swojej metody. Nie powinno to mieć żadnego znaczącego wpływu na wydajność (w przeciwieństwie do Task.Yield ()).
Bob Meyers,
3
@AndrewTheken: W dzisiejszych czasach możesz po prostu zrobić return Task.CompletedTask;- najprostsze ze wszystkich.
Stephen Cleary
65

Inną opcją, jeśli chcesz zachować prostą treść funkcji i nie pisać kodu obsługującego ją, jest po prostu pominięcie ostrzeżenia za pomocą #pragma:

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

Jeśli jest to wystarczająco powszechne, możesz umieścić instrukcję disable na początku pliku i pominąć przywracanie.

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

Jamie Macia
źródło
43

Innym sposobem zachowania słowa kluczowego async (na wypadek, gdybyś chciał je zachować) jest użycie:

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

Po wypełnieniu metody możesz po prostu usunąć instrukcję. Używam tego często, zwłaszcza gdy metoda może na coś czekać, ale nie każda implementacja to robi.

Simon Mattes
źródło
To powinna być akceptowana odpowiedź. Czasami implementacje interfejsu nie muszą być asynchroniczne, jest to znacznie czystsze niż zawijanie wszystkiego w Task.Runwywołaniu.
Andrew Theken
12
await Task.CompletedTask; // może być lepszą opcją
Frode Nilsen
@FrodeNilsen z jakiegoś powodu Task.CompletedTaskjuż nie istnieje.
Sebastián Vansteenkiste
1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, .Net Core 1.0->
Frode Nilsen
1
@AndrewTheken Zajęło mi trochę czasu, zanim doszedłem do wniosku, że ta odpowiedź i Twój komentarz dotyczą konkretnie przypadku, w którym implementacja jest pusta lub po prostu zgłasza wyjątek (jak w oryginalnym pytaniu). Jeśli implementacja zwraca wartość, wydaje się, że Task.FromResultjest to lepsza odpowiedź. O to chodzi, jeśli rzuci wyjątek, wydaje się inna odpowiedź nie wchodzą w grę w odniesieniu do Task.FromExceptionpodejmowania tego nigdy rozwiązanie idealne. Zgodziłbyś się?
BlueMonkMN
15

Istnieje różnica między rozwiązaniami i mówiąc ściśle, powinieneś wiedzieć, w jaki sposób wywołujący wywoła metodę asynchroniczną, ale z domyślnym wzorcem użycia, który zakłada ".Wait ()" na wyniku metody - " return Task.CompletedTask " jest najlepszym rozwiązaniem.

    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 |

Uwaga: FromResultnie można bezpośrednio porównać.

Kod testu:

   [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
źródło
1
Szkoda, że #pragmajeden wydaje się ponosić koszty ogólne. Prawdopodobnie tyle samo narzutu, jakbyś zamiast zwrotu CompletedTaskutworzył i ukończył plik AsyncOperation. Byłoby miło móc powiedzieć kompilatorowi, że można to pominąć, gdy metoda i tak działa synchronicznie.
binki
Jak myślisz, jak Task.CompletedTaskpodobne jest podobne do Task.FromResult? Ciekawe byłoby wiedzieć - spodziewam się, że FromResult byłby najbardziej analogiczny i nadal miałby najlepsze wyniki, gdyby trzeba było zwrócić wartość.
BlueMonkMN
Dodam to. Myślę stan kodu maszyna będzie więcej komunikatów w tej sprawie i CompletedTask wygra .Let zobaczyć
Roman Pokrovskij
1
Byłoby miło zobaczyć to zaktualizowane dla .NET Core 2.2, ponieważ alokacje w maszynach stanu asynchronicznego zostały drastycznie ulepszone
Tseng
1
@Tseng Przeprowadziłem testy porównawcze na .NET Core 2.2.0. Oczywiście całkowity czas jest inny ze względu na inny sprzęt, ale stosunek pozostaje mniej więcej taki sam: Metoda | .NET Core 2.0.3 Średnia | .NET Core 2.2.0 Średnia ukończona | 100% | Ukończono w 100% Czekaj | 412,57% | 377,22% FromResult | 520,72% | 590,89% Pragma | 378,37% | 346,64% Wydajność | 27514,47% | 23602,38%
Storm,
10

Wiem, że to stary wątek i być może nie będzie to miało odpowiedniego efektu dla wszystkich zastosowań, ale poniższe informacje są tak bliskie, jak tylko mogę, aby móc po prostu wyrzucić NotImplementedException, gdy nie zaimplementowałem jeszcze metody, bez zmiany sygnatury metody. Jeśli jest to problematyczne, chętnie o tym dowiem się, ale to nie ma dla mnie znaczenia: i tak używam tego tylko podczas tworzenia, więc to, jak działa, nie jest aż tak ważne. Mimo wszystko z przyjemnością usłyszę o tym, dlaczego to zły pomysł, jeśli tak jest.

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

Oto typ, który dodałem, aby to umożliwić.

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
źródło
10

Podobnie jak aktualizacja odpowiedzi Stephena, nie musisz już pisać tej TaskConstantsklasy, ponieważ istnieje nowa metoda pomocnicza:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
Matt
źródło
3
Nie rób tego. Ślad stosu nie będzie wskazywał na kod. Wyjątki muszą zostać wyrzucone, aby zostały całkowicie zainicjowane.
Daniel B
1
Daniel B - Tak, masz całkowitą rację. Zmodyfikowałem odpowiedź, aby poprawnie zgłosić wyjątek.
Matt,
3

Jeśli już łączysz się z Reactive Extension, możesz również:

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

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

Reaktywne i async / await są niesamowite same w sobie i same w sobie, ale także dobrze grają razem.

Potrzebne obejmuje:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
Jan
źródło
3

Może się to zdarzyć cs1998 poniżej.

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

Następnie możesz zreformować poniżej.

public async Task<object> Foo()
{
    var result = await Task.Run(() =>
    {
        return object;
    });
    return result;
}
瀧 谷 賢 司
źródło
3

Możesz spróbować tego:

public async Task<object> test()
{
await Task.CompletedTask; 
}
Faisal Mehboob
źródło
1

Jeśli nie masz nic do czekania, zwróć Task.FromResult

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

Oto kilka alternatyw w zależności od podpisu metody.

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

Możesz usunąć słowo kluczowe async z metody i po prostu zwrócić 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
źródło