peringatan panggilan ini tidak ditunggu, pelaksanaan metode saat ini berlanjut

135

Baru saja mendapat VS2012 dan mencoba menangani async.

Katakanlah saya punya metode yang mengambil beberapa nilai dari sumber pemblokiran. Saya tidak ingin penelepon metode untuk memblokir. Saya bisa menulis metode untuk mengambil callback yang dipanggil ketika nilainya tiba, tetapi karena saya menggunakan C # 5, saya memutuskan untuk membuat metode async sehingga penelepon tidak harus berurusan dengan callback:

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

Inilah contoh metode yang menyebutnya. Jika PromptForStringAsynctidak async, metode ini akan membutuhkan peneleponan kembali di dalam panggilan balik. Dengan async, saya bisa menulis metode saya dengan cara yang sangat alami ini:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

Sejauh ini baik. Masalahnya adalah ketika saya memanggil GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

Intinya GetNameAsyncadalah asinkron. Saya tidak ingin memblokir, karena saya ingin kembali ke MainWorkOfApplicationIDontWantBlocked ASAP dan biarkan GetNameAsync melakukan hal itu di latar belakang. Namun, menyebutnya dengan cara ini memberi saya peringatan kompiler di GetNameAsynctelepon:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

Saya sangat menyadari bahwa "eksekusi metode saat ini berlanjut sebelum panggilan selesai". Itulah titik kode asynchronous, kan?

Saya lebih suka kode saya dikompilasi tanpa peringatan, tetapi tidak ada yang bisa "diperbaiki" di sini karena kode melakukan apa yang saya inginkan. Saya bisa menghilangkan peringatan dengan menyimpan nilai pengembalian GetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

Tapi sekarang saya punya kode berlebihan. Visual Studio tampaknya mengerti bahwa saya dipaksa untuk menulis kode yang tidak perlu ini, karena menekan peringatan "nilai tidak pernah digunakan" yang normal.

Saya juga bisa menghilangkan peringatan dengan membungkus GetNameAsync dengan metode yang tidak async:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

Tapi itu kode yang lebih berlebihan. Jadi saya harus menulis kode saya tidak perlu atau mentolerir peringatan yang tidak perlu.

Apakah ada sesuatu tentang penggunaan async yang salah di sini?

Lumpur
sumber
2
BTW, ketika menerapkan PromptForStringAsyncAnda melakukan lebih banyak pekerjaan daripada yang Anda butuhkan; kembalikan saja hasil Task.Factory.StartNew. Sudah tugas siapa yang nilainya adalah string yang dimasukkan di konsol. Tidak perlu menunggu untuk mengembalikan hasilnya; hal itu tidak menambah nilai baru.
Servy
Bukankah lebih masuk akal untuk GetNameAsyncmemberikan nama lengkap yang disediakan oleh pengguna (yaitu Task<Name>, daripada hanya mengembalikan Task? DoStuffLalu dapat menyimpan tugas itu, dan apakah awaititu setelah metode lain, atau bahkan meneruskan tugas ke yang lain metode sehingga bisa awaitatau di Waitsuatu tempat di dalam implementasi itu
Servy
@Ervy: Jika saya hanya mengembalikan tugas, saya mendapatkan kesalahan "Karena ini adalah metode async, ekspresi kembali harus bertipe 'string' daripada 'Task <string>'".
Lumpur
1
Hapus asynckata kunci.
Servy
13
IMO, ini adalah pilihan yang buruk untuk peringatan pada bagian dari tim C #. Peringatan harus untuk hal-hal yang hampir pasti salah. Ada banyak kasus di mana Anda ingin "memecat dan melupakan" metode async, dan di lain waktu Anda benar-benar ingin menunggunya.
MgSam

Jawaban:

103

Jika Anda benar-benar tidak membutuhkan hasilnya, Anda cukup mengubah GetNameAsynctanda tangan untuk mengembalikan void:

public static async void GetNameAsync()
{
    ...
}

Pertimbangkan untuk melihat jawaban atas pertanyaan terkait: Apa perbedaan antara mengembalikan batal dan mengembalikan Tugas?

Memperbarui

Jika Anda membutuhkan hasilnya, Anda dapat mengubah GetNameAsyncuntuk kembali, katakan Task<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

Dan gunakan sebagai berikut:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}
Nikolay Khil
sumber
15
Itu hanya akan menjadi masalah jika dia tidak pernah peduli dengan hasilnya, bukan hanya tidak perlu hasilnya kali ini .
Servy
3
@Ervy, benar, terima kasih atas klarifikasi. Tapi OP GetNameAsynctidak mengembalikan nilai apa pun (kecuali hasilnya sendiri, tentu saja).
Nikolay Khil
2
Benar, tetapi dengan mengembalikan tugas dia bisa tahu kapan selesai menjalankan semua operasi async. Jika itu kembali void, dia tidak memiliki cara untuk mengetahui kapan itu dilakukan. Itulah yang saya maksud ketika saya mengatakan "hasil" dalam komentar saya sebelumnya.
Servy
29
Sebagai aturan umum, Anda tidak boleh memiliki async voidmetode kecuali untuk penangan acara.
Daniel Mann
2
Hal lain yang perlu diperhatikan di sini adalah bahwa perilaku berbeda ketika datang ke pengecualian yang tidak diamati ketika Anda beralih ke async voidpengecualian yang tidak Anda tangkap akan merusak proses Anda, tetapi dalam .net 4.5 itu akan tetap berjalan.
Caleb Vear
62

Saya cukup terlambat untuk diskusi ini, tetapi ada juga opsi untuk menggunakan #pragmaarahan pra-prosesor. Saya memiliki beberapa kode async di sana-sini yang secara eksplisit saya tidak ingin menunggu dalam beberapa kondisi, dan saya tidak menyukai peringatan dan variabel yang tidak digunakan sama seperti Anda semua:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

Itu "4014"datang dari halaman MSDN ini: Peringatan Kompiler (level 1) CS4014 .

Lihat juga peringatan / jawaban oleh @ ryan-horath di sini https://stackoverflow.com/a/12145047/928483 .

Pengecualian yang dilemparkan selama panggilan async yang tidak ditunggu akan hilang. Untuk menghilangkan peringatan ini, Anda harus menetapkan nilai pengembalian tugas panggilan async ke variabel. Ini memastikan Anda memiliki akses ke pengecualian yang dilontarkan, yang akan ditunjukkan dalam nilai pengembalian.

Pembaruan untuk C # 7.0

C # 7.0 menambahkan fitur baru, membuang variabel: Buang - Panduan C # , yang juga dapat membantu dalam hal ini.

_ = SomeMethodAsync();
Willem
sumber
5
Benar-benar hebat. Saya tidak tahu Anda bisa melakukan ini. Terima kasih!
Maxim Gershkovich
1
Bahkan tidak diharuskan untuk mengatakan var, cukup tulis_ = SomeMethodAsync();
Ray
41

Saya tidak terlalu menyukai solusi yang baik menetapkan tugas ke variabel yang tidak digunakan, atau mengubah metode tanda tangan untuk mengembalikan batal. Yang pertama membuat berlebihan, kode non-intuitif, sedangkan yang terakhir mungkin tidak dapat dilakukan jika Anda mengimplementasikan antarmuka atau menggunakan fungsi lain di mana Anda ingin menggunakan Tugas yang dikembalikan.

Solusi saya adalah membuat metode ekstensi dari Task, yang disebut DoNotAwait () yang tidak melakukan apa-apa. Ini tidak hanya akan menekan semua peringatan, ReSharper atau lainnya, tetapi membuat kode lebih mudah dipahami, dan menunjukkan kepada pengelola kode Anda di masa mendatang bahwa Anda benar-benar bermaksud agar panggilan tidak ditunggu.

Metode ekstensi:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

Pemakaian:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

Diedit untuk menambahkan: ini mirip dengan solusi Jonathan Allen di mana metode ekstensi akan memulai tugas jika belum dimulai, tetapi saya lebih suka memiliki fungsi tujuan tunggal sehingga maksud pemanggil benar-benar jelas.

Joe Savage
sumber
1
Saya suka ini, tetapi saya menamainya menjadi Unawait ();)
Ostati
29

async void BURUK!

  1. Apa perbedaan antara mengembalikan batal dan mengembalikan Tugas?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Apa yang saya sarankan adalah Anda menjalankan secara eksplisit Taskmelalui metode anonim ...

misalnya

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

Atau jika Anda memang ingin memblokirnya Anda bisa menunggu dengan metode anonim

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

Namun, jika GetNameAsyncmetode Anda harus berinteraksi dengan UI atau bahkan apa pun yang terikat UI, (WINRT / MVVM, saya melihat Anda), maka itu akan menjadi sedikit lebih lucu =)

Anda harus meneruskan referensi ke dispatcher UI seperti ini ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

Dan kemudian dalam metode async Anda, Anda harus berinteraksi dengan UI atau elemen terikat UI berpikir bahwa pengirim ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });
MrE
sumber
Ekstensi Tugas Anda luar biasa. Tidak tahu mengapa saya belum menerapkannya sebelumnya.
Mikael Dúi Bolinder
8
Pendekatan pertama yang Anda sebutkan menyebabkan peringatan yang berbeda: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. Ini juga menyebabkan utas baru dibuat, sedangkan utas baru tidak harus dibuat dengan async / tunggu sendiri.
gregsdennis
Anda harus bangun pak!
Ozkan
16

Inilah yang sedang saya lakukan:

SomeAyncFunction().RunConcurrently();

Di mana RunConcurrentlydidefinisikan sebagai ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/

Jonathan Allen
sumber
1
+1. Sepertinya ini menghindari semua masalah yang dibandingkan dalam komentar dari jawaban lain. Terima kasih.
Grault
3
Anda hanya perlu ini: public static void Forget(this Task task) { }
Shital Shah
1
apa maksudmu di sana @ ShitalShah
Jay Wick
@ShitalShah yang hanya akan bekerja dengan tugas memulai otomatis seperti yang dibuat dengan async Task. Beberapa tugas harus dimulai secara manual.
Jonathan Allen
7

Menurut artikel Microsoft pada peringatan ini, Anda bisa menyelesaikannya dengan hanya menetapkan tugas yang dikembalikan ke variabel. Di bawah ini adalah terjemahan dari kode yang disediakan dalam contoh Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

Perhatikan bahwa melakukan ini akan menghasilkan pesan "Variabel lokal tidak pernah digunakan" di ReSharper.

dewa
sumber
Ya, itu menekan peringatan, tetapi tidak, itu tidak benar-benar menyelesaikan apa pun. Task-mengembalikan fungsi harus await-ed kecuali Anda punya alasan yang sangat bagus untuk tidak melakukannya. Tidak ada alasan di sini mengapa membuang tugas akan lebih baik daripada jawaban yang sudah diterima menggunakan async voidmetode.
Saya juga lebih suka metode void. Ini membuat StackOverflow lebih pintar dari Microsoft, tetapi tentu saja itu harus diberikan.
devlord
4
async voidmemperkenalkan masalah serius seputar penanganan kesalahan dan menghasilkan kode yang tidak dapat diuji (lihat artikel MSDN saya ). Akan jauh lebih baik menggunakan variabel - jika Anda benar - benar yakin Anda ingin pengecualian ditelan diam-diam. Lebih mungkin, op ingin memulai dua Taskdan kemudian melakukan await Task.WhenAll.
Stephen Cleary
@StephenCleary Apakah pengecualian dari tugas yang tidak teramati diabaikan atau dapat dikonfigurasi. Jika ada kemungkinan kode Anda akan digunakan dalam proyek orang lain, jangan biarkan itu terjadi. async void DoNotWait(Task t) { await t; }Metode penolong sederhana dapat digunakan untuk menghindari kelemahan async voidmetode yang Anda gambarkan. (Dan saya tidak berpikir Task.WhenAllapa yang diinginkan OP, tapi bisa saja.)
@ hvd: Metode pembantu Anda akan membuatnya dapat diuji, tetapi metode ini masih memiliki dukungan penanganan pengecualian yang sangat buruk.
Stephen Cleary
3

Di sini, solusi sederhana.

public static class TasksExtensions
{
    public static void RunAndForget(this Task task)
    {
    }
}

Salam

JuanluElGuerre
sumber
1

Ini contoh sederhana Anda yang menyebabkan kode superflous. Biasanya Anda ingin menggunakan data yang diambil dari sumber pemblokiran di beberapa titik dalam program, jadi Anda ingin hasilnya kembali sehingga mungkin untuk mendapatkan data.

Jika Anda benar-benar memiliki sesuatu yang terjadi sepenuhnya terisolasi dari sisa program, async tidak akan menjadi pendekatan yang tepat. Mulai saja utas baru untuk tugas itu.

Guffa
sumber
Saya lakukan ingin menggunakan data yang diambil dari sumber memblokir, tapi saya tidak ingin memblokir penelepon sementara aku menunggu untuk itu. Tanpa async, Anda dapat melakukannya dengan mengirimkan panggilan balik. Dalam contoh saya, saya memiliki dua metode asinkron yang perlu dipanggil secara berurutan, dan panggilan kedua perlu menggunakan nilai yang dikembalikan oleh yang pertama. Ini akan berarti penangan panggilan balik bersarang, yang menjadi jelek sekali. Dokumentasi yang saya baca menunjukkan bahwa ini secara khusus asyncdirancang untuk membersihkan ( misalnya )
Mud
@Mud: Kirimkan saja hasil dari panggilan async pertama sebagai parameter ke panggilan kedua. Dengan begitu kode dalam metode kedua dimulai segera, dan itu bisa menunggu hasil dari panggilan pertama ketika rasanya seperti itu.
Guffa
Saya bisa melakukan itu, tetapi tanpa async itu berarti panggilan balik bersarang, seperti ini: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Bahkan dalam contoh sepele ini, itu menyebalkan untuk diurai. Dengan async, kode yang setara dihasilkan untuk saya ketika saya menulis result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); Yang jauh lebih mudah dibaca (meskipun tidak ada yang sangat mudah dibaca dihancurkan bersama dalam komentar ini!)
Mud
@Mud: Ya. Tetapi Anda dapat memanggil metode async kedua tepat setelah yang pertama, tidak ada alasan untuk itu menunggu panggilan sinkron Use.
Guffa
Saya benar-benar tidak mengerti apa yang Anda katakan. MethodWithCallback asinkron. Jika saya memanggil mereka kembali ke belakang tanpa menunggu panggilan pertama, panggilan kedua mungkin selesai sebelum yang pertama. Namun, saya membutuhkan hasil panggilan pertama di handler untuk panggilan kedua. Jadi saya harus menunggu panggilan pertama.
Lumpur
0

Apakah Anda benar-benar ingin mengabaikan hasilnya? seperti termasuk mengabaikan pengecualian yang tidak terduga?

Jika tidak, Anda mungkin ingin melihat pertanyaan ini: Api dan Lupakan pendekatan ,

Jens
sumber
0

Jika Anda tidak ingin mengubah tanda tangan metode untuk kembali void(karena pengembalian voidharus selalu dibatalkan ), Anda dapat menggunakan fitur C # 7.0+ Buang seperti ini, yang sedikit lebih baik daripada menetapkan ke variabel (dan harus menghapus sebagian besar lainnya) peringatan alat validasi sumber):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Simon Mourier
sumber