Sembunyikan peringatan CS1998: Metode asinkron ini tidak memiliki 'menunggu'

104

Saya punya antarmuka dengan beberapa fungsi asinkron. Beberapa kelas yang mengimplementasikan antarmuka tidak memiliki apa-apa untuk menunggu, dan beberapa mungkin hanya melempar. Agak mengganggu dengan semua peringatannya.

Saat tidak menggunakan await dalam fungsi async.

Apakah mungkin untuk menyembunyikan pesan tersebut?

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

peringatan CS1998: Metode async ini tidak memiliki operator 'await' dan akan berjalan secara sinkron. Pertimbangkan untuk menggunakan operator 'await' untuk menunggu panggilan API non-pemblokiran, atau 'await Task.Run (...)' untuk melakukan pekerjaan yang terikat dengan CPU pada thread latar belakang.

Simon
sumber
1
Saat tidak menggunakan kata kunci await baru dalam fungsi yang ditandai sebagai async.
Simon
Bagaimana jika menunjukkan kepada kami contoh kode yang mereproduksi masalah?
John Saunders

Jawaban:

106

Saya punya antarmuka dengan beberapa fungsi asinkron.

Metode kembali Task, saya yakin. asyncadalah detail implementasi, jadi tidak bisa diterapkan ke metode antarmuka.

Beberapa kelas yang mengimplementasikan antarmuka tidak memiliki apa-apa untuk menunggu, dan beberapa mungkin hanya melempar.

Dalam kasus ini, Anda dapat memanfaatkan fakta yang asyncmerupakan detail implementasi.

Jika Anda tidak punya apa-apa await, maka Anda bisa kembali Task.FromResult:

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

Dalam kasus melempar NotImplementedException, prosedurnya sedikit lebih bertele-tele:

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

Jika Anda memiliki banyak metode melempar NotImplementedException(yang dengan sendirinya dapat menunjukkan bahwa beberapa pemfaktoran ulang tingkat desain akan baik), maka Anda dapat membungkus wordiness menjadi kelas pembantu:

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

Kelas pembantu juga mengurangi sampah yang GC dinyatakan akan harus mengumpulkan, karena masing-masing metode dengan tipe kembali sama dapat berbagi nya Taskdan NotImplementedExceptionbenda-benda.

Saya memiliki beberapa contoh jenis "konstanta tugas" lainnya di perpustakaan AsyncEx saya .

Stephen Cleary
sumber
1
Saya tidak berpikir kehilangan kata kunci. Seperti yang Anda katakan, async tidak ada hubungannya dengan antarmuka. Saya buruk, terima kasih.
Simon
3
Dapatkah Anda merekomendasikan pendekatan di mana jenis kembalian hanya Tugas (tanpa hasil?)
Mike
9
Peringatan: Pendekatan ini dapat menyebabkan masalah karena kesalahan tidak akan disebarkan seperti yang Anda harapkan. Biasanya pemanggil akan mengharapkan pengecualian dalam metode Anda untuk dimunculkan dalam file Task. Sebaliknya, metode Anda akan membuang bahkan sebelum mendapat kesempatan untuk membuat file Task. Menurut saya pola terbaik adalah mendefinisikan asyncmetode tanpa awaitoperator. Ini memastikan kode di dalam metode semua diperlakukan sebagai bagian dari Task.
Bob Meyers
11
Untuk menghindari CS1998, Anda dapat menambahkan await Task.FromResult(0);metode Anda. Ini seharusnya tidak memiliki dampak kinerja yang signifikan (tidak seperti Task.Yield ()).
Bob Meyers
3
@AndrewTheken: Hari-hari ini Anda hanya bisa melakukan return Task.CompletedTask;- yang paling sederhana dari semuanya.
Stephen Cleary
63

Opsi lain, jika Anda ingin menjaga isi fungsi tetap sederhana dan tidak menulis kode untuk mendukungnya, cukup dengan menekan peringatan dengan #pragma:

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

Jika ini cukup umum, Anda dapat meletakkan pernyataan nonaktifkan di bagian atas file dan menghilangkan pemulihan.

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

Jamie Macia
sumber
40

Cara lain untuk mempertahankan kata kunci async (jika Anda ingin menyimpannya) adalah dengan menggunakan:

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

Setelah Anda mengisi metode, Anda cukup menghapus pernyataan tersebut. Saya sering menggunakan ini terutama ketika sebuah metode mungkin menunggu sesuatu tetapi tidak setiap implementasi benar-benar melakukannya.

Simon Mattes
sumber
Ini harus menjadi jawaban yang diterima. Terkadang implementasi antarmuka tidak perlu asinkron, ini jauh lebih bersih daripada menggabungkan semuanya dalam Task.Runpanggilan.
Andrew Theken
12
menunggu Task.CompletedTask; // mungkin pilihan yang lebih baik
Frode Nilsen
@FrodeNilsen untuk beberapa alasan Task.CompletedTasksepertinya tidak ada lagi.
Sebastián Vansteenkiste
1
@ SebastiánVansteenkiste .Net Framework 4.6->, UWP 1.0->, .Net Core 1.0->
Frode Nilsen
1
@AndrewTheken Butuh beberapa saat bagi saya untuk mencapai kesimpulan bahwa jawaban dan komentar Anda ini berlaku khusus untuk kasus di mana penerapannya kosong atau hanya melontarkan pengecualian (seperti dalam pertanyaan asli). Jika suatu implementasi mengembalikan nilai, sepertinya itu Task.FromResultadalah jawaban yang lebih baik. Untuk itu, jika Anda sedang akan melemparkan pengecualian, tampaknya jawaban lain telah datang ke dalam bermain tentang Task.FromExceptionmembuat tidak pernah solusi ideal ini. Apakah anda setuju
BlueMonkMN
15

Ada perbedaan antara solusi dan secara tegas Anda harus tahu bagaimana pemanggil akan memanggil metode async, tetapi dengan pola penggunaan default yang mengasumsikan ".Wait ()" pada hasil metode - " return Task.CompletedTask " adalah solusi terbaik.

    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 |

Catatan: FromResulttidak bisa dibandingkan secara langsung.

Kode Tes:

   [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
sumber
1
Sangat disayangkan bahwa yang #pragmasatu itu tampaknya menimbulkan biaya tambahan. Mungkin sama banyaknya dengan jika alih-alih mengembalikan CompletedTaskAnda membuat dan menyelesaikan file AsyncOperation. Akan menyenangkan untuk dapat memberi tahu kompiler bahwa tidak masalah untuk melewatkannya ketika metode tetap berjalan secara sinkron.
binki
Bagaimana serupa yang Anda kira Task.CompletedTaskmirip dengan Task.FromResult? Menarik untuk diketahui - saya berharap FromResult akan menjadi yang paling analog dan tetap menjadi yang terbaik jika Anda harus mengembalikan suatu nilai.
BlueMonkMN
Saya akan menambahkannya. Saya pikir kode mesin negara akan lebih bertele-tele dalam kasus ini dan CompletedTask akan menang. Mari kita lihat
Roman Pokrovskij
1
Akan menyenangkan melihat ini diperbarui untuk .NET Core 2.2, karena alokasi di mesin negara asinkron telah ditingkatkan secara drastis
Tseng
1
@Tseng Saya telah menjalankan benchmark pada .NET Core 2.2.0. Jelas, total waktu berbeda karena perangkat keras yang berbeda, tetapi rasionya tetap kurang lebih sama: Metode | .NET Core 2.0.3 Berarti | .NET Core 2.2.0 Berarti Selesai | 100% | 100% SelesaiMenunggu | 412.57% | 377.22% FromResult | 520,72% | 590,89% Pragma | 378,37% | Hasil 346.64% | 27514,47% | 23602.38%
Badai
10

Saya tahu ini adalah utas lama, dan mungkin ini tidak akan memiliki efek yang tepat untuk semua penggunaan, tetapi berikut ini sedekat yang saya bisa untuk bisa dengan mudah melempar NotImplementedException ketika saya belum menerapkan metode, tanpa mengubah tanda tangan metode. Jika bermasalah, saya akan senang mengetahuinya, tetapi itu hampir tidak penting bagi saya: Saya hanya menggunakan ini saat dalam pengembangan, jadi bagaimana kinerjanya tidak terlalu penting. Namun, saya akan senang mendengar mengapa itu ide yang buruk, jika memang demikian.

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

Inilah tipe yang saya tambahkan untuk memungkinkannya.

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

Sama seperti pembaruan Stephen's Answer, Anda tidak perlu lagi menulis TaskConstantskelas karena ada metode pembantu baru:

    public Task ThrowException()
    {
        try
        {
            throw new NotImplementedException();
        }
        catch (Exception e)
        {
            return Task.FromException(e);
        }
    }
Matt
sumber
3
Jangan lakukan ini. Pelacakan tumpukan tidak akan mengarah ke kode Anda. Pengecualian harus dilemparkan untuk diinisialisasi sepenuhnya.
Daniel B
1
Daniel B - Ya, Anda benar sekali. Saya telah mengubah jawaban saya untuk membuang pengecualian dengan benar.
Matt
3

Jika Anda sudah menautkan ke Reactive Extension, Anda juga dapat melakukan:

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

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

Reaktif dan asinkron / menunggu keduanya luar biasa dalam dan dengan sendirinya, tetapi mereka juga bermain bersama dengan baik.

Termasuk yang dibutuhkan adalah:

using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
John
sumber
3

Mungkin terjadi cs1998 di bawah ini.

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

Kemudian Anda dapat mereformasi di bawah.

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

Anda bisa mencoba ini:

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

Jika Anda tidak memiliki apa-apa untuk menunggu, kembalikan Task.FromResult

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

Berikut beberapa alternatif tergantung pada tanda tangan metode Anda.

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

Anda dapat melepaskan kata kunci async dari metode dan mengembalikannya ke 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
sumber