Menekan "peringatan CS4014: Karena panggilan ini tidak ditunggu, eksekusi metode saat ini berlanjut ..."

156

Ini bukan duplikat dari "Cara memanggil metode async dengan aman di C # tanpa menunggu" .

Bagaimana cara saya dengan baik menekan peringatan berikut?

peringatan CS4014: Karena panggilan ini tidak ditunggu, pelaksanaan metode saat ini berlanjut sebelum panggilan selesai. Pertimbangkan untuk menerapkan operator 'menunggu' ke hasil panggilan.

Contoh sederhana:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

Apa yang saya coba dan tidak suka:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

Diperbarui , karena jawaban yang diterima asli telah diedit, saya telah mengubah jawaban yang diterima menjadi yang menggunakan C # 7.0 discards , karena menurut saya tidak ContinueWithsesuai di sini. Kapan pun saya perlu mencatat pengecualian untuk operasi kebakaran dan lupakan, saya menggunakan pendekatan yang lebih rumit yang diusulkan oleh Stephen Cleary di sini .

noseratio
sumber
1
Jadi, menurut Anda #pragmatidak baik?
Frédéric Hamidi
10
@ FrédéricHamidi, saya tahu.
noseratio
2
@Noseratio: Ah, benar. Maaf, saya pikir itu peringatan lain. Abaikan saya!
Jon Skeet
3
@Tribribad: Saya tidak begitu yakin - sepertinya peringatan itu cukup masuk akal untuk sebagian besar kasus. Secara khusus, Anda harus berpikir tentang apa yang Anda inginkan terjadi pada kegagalan - biasanya bahkan untuk "api dan lupakan" Anda harus mengetahui cara mencatat kegagalan dll.
Jon Skeet
4
@Terribad, sebelum Anda menggunakannya dengan cara ini atau yang lain, Anda harus memiliki gambaran yang jelas tentang bagaimana pengecualian disebarkan untuk metode async (centang ini ). Kemudian, jawaban oleh @ Knaģis memberikan cara yang elegan untuk tidak kehilangan pengecualian untuk api-dan-lupa, melalui async voidmetode pembantu.
noseratio

Jawaban:

160

Dengan C # 7 sekarang Anda dapat menggunakan discards :

_ = WorkAsync();
Anthony Wieser
sumber
7
Ini adalah fitur bahasa kecil yang tidak bisa saya ingat. Sepertinya ada yang ada _ = ...di otakku.
Marc L.
3
Saya menemukan SupressMessage menghapus peringatan saya dari "Daftar Kesalahan" Visual Studio saya tetapi tidak "Keluaran" dan #pragma warning disable CSxxxxterlihat lebih jelek daripada yang dibuang;)
David Savage
122

Anda dapat membuat metode ekstensi yang akan mencegah peringatan. Metode ekstensi bisa kosong atau Anda bisa menambahkan penanganan pengecualian di .ContinueWith()sana.

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

Namun ASP.NET menghitung jumlah tugas yang sedang berjalan, sehingga tidak akan bekerja dengan Forget()ekstensi sederhana seperti yang tercantum di atas dan malah gagal dengan pengecualian:

Modul atau pengendali asinkron selesai ketika operasi asinkron masih tertunda.

Dengan .NET 4.5.2 dapat diselesaikan dengan menggunakan HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}
Knaģis
sumber
8
Saya telah menemukan TplExtensions.Forget. Ada banyak kebaikan di bawah Microsoft.VisualStudio.Threading. Saya berharap ini tersedia untuk digunakan di luar Visual Studio SDK.
noseratio
1
@Noseratio, dan Knagis, saya suka pendekatan ini dan saya berencana untuk menggunakannya. Saya memposting pertanyaan tindak lanjut terkait: stackoverflow.com/questions/22864367/fire-and-forget-approach
Matt Smith
3
@stricq Apa tujuan menambahkan ConfigureAwait (false) ke Forget () serve? Seperti yang saya pahami, ConfigureAwait hanya memengaruhi sinkronisasi utas pada titik di mana menunggu digunakan pada Tugas, tetapi tujuan Forget () adalah untuk membuang Tugas, sehingga Tugas tidak pernah dapat ditunggu, sehingga ConfigureAwait di sini tidak ada gunanya.
dthorpe
3
Jika spawning thread hilang sebelum kebakaran dan melupakan tugas selesai, tanpa ConfigureAwait (false) masih akan mencoba untuk menyusun kembali ke thread spawning, thread yang hilang sehingga deadlock. Pengaturan ConfigureAwait (false) memberi tahu sistem untuk tidak marshall kembali ke utas panggilan.
stricq
2
Balasan ini memiliki suntingan untuk mengelola kasus tertentu, ditambah selusin komentar. Hal-hal sederhana seringkali merupakan hal-hal yang benar, gunakan untuk dibuang! Dan saya kutip @ fjch1997 menjawab: Adalah bodoh untuk membuat metode yang membutuhkan beberapa kutu untuk dieksekusi, hanya untuk tujuan menekan peringatan.
Teejay
39

Anda dapat menghias metode ini dengan atribut berikut:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

Pada dasarnya Anda memberi tahu kompiler bahwa Anda tahu apa yang Anda lakukan dan tidak perlu khawatir tentang kemungkinan kesalahan.

Bagian penting dari kode ini adalah parameter kedua. Bagian "CS4014:" adalah yang menekan peringatan. Anda dapat menulis apa pun yang Anda inginkan di sisanya.

Fábio
sumber
Tidak berfungsi untuk saya: Visual Studio untuk Mac 7.0.1 (build 24). Sepertinya harus tetapi - tidak.
IronRod
1
[SuppressMessage("Compiler", "CS4014")]menekan pesan di jendela Daftar Kesalahan, tetapi jendela Output masih menampilkan garis peringatan
David Ching
35

Dua cara saya berurusan dengan ini.

Simpan ke variabel buang (C # 7)

Contoh

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

Sejak diperkenalkannya discards di C # 7, saya sekarang menganggap ini lebih baik daripada menekan peringatan. Karena itu tidak hanya menekan peringatan, tetapi juga membuat niat api-dan-lupa menjadi jelas.

Selain itu, kompiler akan dapat mengoptimalkannya dalam mode rilis.

Tekan saja

#pragma warning disable 4014
...
#pragma warning restore 4014

adalah solusi yang cukup bagus untuk "memecat dan melupakan".

Alasan mengapa peringatan ini ada adalah karena dalam banyak kasus itu bukan niat Anda untuk menggunakan metode yang mengembalikan tugas tanpa menunggu itu. Menekan peringatan ketika Anda berniat untuk menembak dan melupakan itu masuk akal.

Jika Anda kesulitan mengingat cara mengeja #pragma warning disable 4014, cukup biarkan Visual Studio menambahkannya untuk Anda. Tekan Ctrl +. untuk membuka "Tindakan Cepat" dan kemudian "Menekan CS2014"

Semua seutuhnya

Adalah bodoh untuk membuat metode yang membutuhkan beberapa kutu untuk dieksekusi, hanya untuk tujuan menekan peringatan.

fjch1997
sumber
Ini bekerja di Visual Studio untuk Mac 7.0.1 (build 24).
IronRod
1
Adalah bodoh untuk membuat metode yang memerlukan beberapa kutu lagi untuk dieksekusi, hanya untuk tujuan menekan peringatan - yang ini tidak menambahkan kutu tambahan sama sekali dan IMO lebih mudah dibaca:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio
1
@Noseratio Banyak kali ketika saya menggunakan AggressiveInliningkompiler hanya mengabaikannya untuk alasan apa pun
fjch1997
1
Saya suka opsi pragma, karena itu super sederhana dan hanya berlaku untuk baris (atau bagian) kode saat ini, bukan keseluruhan metode.
wasatchwizard
2
Jangan lupa untuk menggunakan kode kesalahan seperti #pragma warning disable 4014dan kemudian mengembalikan peringatan setelahnya dengan #pragma warning restore 4014. Masih berfungsi tanpa kode kesalahan, tetapi jika Anda tidak menambahkan nomor kesalahan itu akan menekan semua pesan.
DunningKrugerEffect
11

Cara mudah untuk menghentikan peringatan adalah dengan hanya menetapkan Tugas saat memanggilnya:

Task fireAndForget = WorkAsync(); // No warning now

Maka dalam posting asli Anda, Anda akan melakukan:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
Noelicus
sumber
Saya menyebutkan pendekatan ini dalam pertanyaan itu sendiri, sebagai salah satu yang tidak saya sukai.
noseratio
Aduh! Tidak memperhatikan itu karena itu di bagian kode yang sama dengan pragma Anda ... Dan saya sedang mencari jawaban. Selain itu, apa yang tidak Anda sukai dari metode ini?
noelicus
1
Saya tidak suka itu taskterlihat seperti variabel lokal yang terlupakan. Hampir seperti kompiler harus memberi saya peringatan lain, sesuatu seperti " taskditugaskan tetapi nilainya tidak pernah digunakan", selain itu tidak. Juga, itu membuat kode lebih mudah dibaca. Saya sendiri menggunakan pendekatan ini .
noseratio
Cukup adil - saya punya perasaan yang sama yang mengapa saya beri nama itu fireAndForget... jadi saya berharap untuk selanjutnya tidak direferensikan.
noelicus
4

Alasan untuk peringatan itu adalah WorkAsync mengembalikan Taskyang tidak pernah dibaca atau ditunggu. Anda dapat mengatur kembali tipe WorkAsync voiddan peringatan akan hilang.

Biasanya metode mengembalikan a Taskketika penelepon perlu mengetahui status pekerja. Dalam kasus api-dan-lupa, kekosongan harus dikembalikan agar menyerupai bahwa penelepon independen dari metode yang disebut.

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}
Pemenang
sumber
2

Mengapa tidak membungkusnya di dalam metode async yang mengembalikan batal? Agak panjang tapi semua variabel digunakan.

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}
Akli
sumber
1

Saya menemukan pendekatan ini secara tidak sengaja hari ini. Anda dapat menentukan delegasi dan menetapkan metode async ke delegasi terlebih dahulu.

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

dan menyebutnya seperti itu

(new IntermediateHandler(AsyncOperation))();

...

Saya pikir itu menarik bahwa kompiler tidak akan memberikan peringatan yang sama persis ketika menggunakan delegasi.

David Beavon
sumber
Tidak perlu mendeklarasikan delegasi, Anda sebaiknya melakukannya (new Func<Task>(AsyncOperation))()meskipun IMO itu masih agak terlalu bertele-tele.
noseratio