uyarı bu çağrı beklenmiyorsa, mevcut yöntemin yürütülmesi devam ediyor


135

VS2012'yi aldım ve halletmeye çalışıyorum async.

Diyelim ki engelleme kaynağından bir değer alan bir yöntemim var. Yöntemin arayanının engellemesini istemiyorum. Değer geldiğinde çağrılan bir geri aramayı alma yöntemini yazabilirim, ancak C # 5 kullandığım için, yöntemi zaman uyumsuz yapmaya karar verdim, böylece arayanlar geri aramalarla uğraşmak zorunda kalmazlar:

// 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();
    });
}

İşte onu çağıran örnek bir yöntem. Eğer PromptForStringAsynczaman uyumsuz değildi, bu yöntem bir geri içerisinde bir geri arama yuva gerektirecektir. Async ile yöntemimi şu çok doğal bir şekilde yazıyorum:

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

Çok uzak çok iyi. Ben ne zaman sorunudur diyoruz GetNameAsync:

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

Bütün mesele, GetNameAsynceşzamansız olmasıdır. Ben yok istiyorum o ASAP MainWorkOfApplicationIDontWantBlocked geri almak istiyorum, çünkü, engellemek ve GetNameAsync arka planda onun şey yapalım. Ancak, bu şekilde çağırmak bana GetNameAsyncsatırda bir derleyici uyarısı veriyor :

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.

"Mevcut yöntemin yürütülmesinin çağrı tamamlanmadan önce devam ettiğinin" tamamen farkındayım. Budur nokta , doğru asenkron kod?

Kodumun uyarı olmadan derlenmesini tercih ederim, ancak burada "düzeltecek" bir şey yok çünkü kod tam olarak yapmak istediğim şeyi yapıyor. Aşağıdakilerin dönüş değerini saklayarak uyarıdan kurtulabilirim GetNameAsync:

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

Ama artık gereksiz kodum var. Visual Studio, normal "hiç kullanılmayan değer" uyarısını bastırdığı için bu gereksiz kodu yazmaya zorlandığımı anlıyor gibi görünüyor.

GetNameAsync'i zaman uyumsuz olmayan bir yöntemde sararak da uyarıdan kurtulabilirim:

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

Ama bu daha da gereksiz bir kod. Bu yüzden kod yazmalıyım, gereksiz bir uyarıya ihtiyacım yok veya hoşgörülü değilim.

Eşzamansız kullanımımla ilgili burada yanlış olan bir şey mi var?


2
BTW, uygularken PromptForStringAsyncgerekenden daha fazla iş yaparsınız; sadece sonucunu döndür Task.Factory.StartNew. Bu zaten konsola girilen dizenin değeri olan bir görevdir. Sonucun geri gelmesini beklemeye gerek yok; bunu yapmak yeni bir değer katmaz.
2013

O daha mantıklı Wwouldn't için GetNameAsyncyani (kullanıcı tarafından sağlandı Tam isim Task<Name>yerine sadece dönen daha Task? DoStuffO zaman bu görevi depolayabilir ve ya awaitonu sonra diğer yöntemde, hatta bu diğer görevi geçmesi yöntem olabilir awaitya Waitda uygulamasının içinde bir yerde.
2013'te

@Servy: Task'ı sadece döndürürsem, bir hata alıyorum "Bu zaman uyumsuz bir yöntem olduğundan, dönüş ifadesi 'Task <string>' yerine 'string' türünde olmalıdır".
Mud

1
asyncAnahtar kelimeyi kaldırın .
2013

14
IMO, bu C # ekibinin uyarısı için kötü bir seçimdi. Uyarılar, neredeyse kesin olarak yanlış olan şeyler için olmalıdır. Zaman uyumsuz bir yöntemi "ateşle ve unut" ve diğer zamanlarda onu gerçekten beklemek istediğiniz birçok durum vardır.
MgSam

Yanıtlar:


103

Sonuca gerçekten ihtiyacınız yoksa, GetNameAsyncgeri dönmek için basitçe imzasını değiştirebilirsiniz void:

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

İlgili bir sorunun cevabını görmeyi düşünün: Boşluğu iade etmek ile bir Görevi geri vermek arasındaki fark nedir?

Güncelleme

Sonuca ihtiyacınız varsa, GetNameAsyncdönüş için değiştirebilirsiniz , örneğin 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;
}

Ve aşağıdaki gibi kullanın:

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

15
Bu, yalnızca sonuca bu seferlik ihtiyaç duymaması yerine, sonucu asla umursamaması durumunda geçerli olacaktır .
2013

3
@Servy, doğru, açıklama için teşekkürler. Ancak OP'ler GetNameAsyncherhangi bir değer döndürmez (tabii ki sonucun kendisi dışında).
Nikolay Khil

2
Doğru, ancak bir görevi geri döndürerek, tüm zaman uyumsuz işlemleri yürütmeyi bitirdiğinde bilebilir. Eğer geri dönerse void, ne zaman bittiğini bilmesinin bir yolu yok. Önceki yorumumda "sonuç" dediğimde bunu kastettim.
2013

30
Genel bir kural olarak, async voidolay işleyicileri dışında hiçbir zaman bir yönteme sahip olmamalısınız .
Daniel Mann

2
Burada dikkat edilmesi gereken bir diğer nokta da, gözlenmeyen istisnalar söz konusu olduğunda davranış farklıdır, async voidyakalayamadığınız herhangi bir istisnaya geçtiğinizde işleminiz çökecektir, ancak .net 4.5'te çalışmaya devam edecektir.
Caleb Vear

62

Bu tartışmaya oldukça geç kaldım, ancak #pragmaön işlemci yönergesini kullanma seçeneği de var . Burada burada bazı koşullarda beklemek istemediğim bazı eşzamansız kodum var ve tıpkı sizler gibi uyarıları ve kullanılmayan değişkenleri sevmiyorum:

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

Şu "4014"MSDN sayfasından gelir: Derleyici Uyarısı (düzey 1) CS4014 .

Ayrıca @ ryan-horath tarafından gönderilen uyarı / yanıta buradan https://stackoverflow.com/a/12145047/928483 bakın .

Beklenmeyen bir zaman uyumsuz çağrı sırasında atılan istisnalar kaybedilecektir. Bu uyarıdan kurtulmak için, zaman uyumsuz çağrının Görev dönüş değerini bir değişkene atamalısınız. Bu, dönüş değerinde gösterilecek olan tüm istisnalara erişmenizi sağlar.

C # 7.0 için güncelleme

C # 7.0 yeni bir özellik ekler, değişkenleri atar: Atar - C # Kılavuzu , bu konuda da yardımcı olabilir.

_ = SomeMethodAsync();

5
Bu kesinlikle harika. Bunu yapabileceğini bilmiyordum. Teşekkür ederim!
Maxim Gershkovich

1
Söylemeye bile gerek yok var, sadece yaz_ = SomeMethodAsync();
Ray

41

Görevi kullanılmayan bir değişkene atayan veya geçersiz döndürmek için yöntem imzasını değiştiren çözümlerden özellikle hoşlanmıyorum. İlki gereksiz, sezgisel olmayan kod oluşturur, ikincisi ise bir arabirim uyguluyorsanız veya döndürülen Görevi kullanmak istediğinizde işlevi başka bir şekilde kullanıyorsanız mümkün olmayabilir.

Benim çözümüm, DoNotAwait () adında hiçbir şey yapmayan bir Task genişletme yöntemi oluşturmaktır. Bu, yalnızca tüm uyarıları, ReSharper'ı veya başka şekilde bastırmakla kalmaz, aynı zamanda kodu daha anlaşılır hale getirir ve kodunuzun gelecekteki bakımcılarına, aramanın gerçekten beklenmemesini istediğinizi belirtir.

Uzatma yöntemi:

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

Kullanımı:

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

Eklemek için düzenlendi: Bu, Jonathan Allen'ın, daha önce başlamamışsa, uzatma yönteminin göreve başlayacağı çözümüne benzer, ancak arayanın amacının tamamen açık olması için tek amaçlı işlevlere sahip olmayı tercih ederim.


1
Bunu beğendim ama adını Unawait ();) olarak yeniden adlandırdım
Ostati

29

async void KÖTÜDÜR!

  1. Geçersizliği iade etmek ile bir Görevi iade etmek arasındaki fark nedir?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

Önerdiğim şey, açıkça Taskanonim bir yöntemle çalıştırmanızdır ...

Örneğin

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

Ya da engellemesini istiyorsanız anonim yöntemi bekleyebilirsiniz.

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

Bununla birlikte, GetNameAsyncyönteminizin UI ile veya hatta UI bağlı herhangi bir şeyle etkileşime girmesi gerekiyorsa (WINRT / MVVM, size bakıyorum), o zaman biraz daha eğlenceli olur =)

Referansı şu şekilde UI dağıtıcısına iletmeniz gerekir ...

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

Ve sonra eşzamansız yönteminizde, dağıtıcının düşündüğü UI veya UI bağlı öğelerinizle etkileşime girmeniz gerekir ...

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

Görev uzantınız harika. Daha önce neden uygulamadığımı bilmiyorum.
Mikael Dúi Bolinder

8
Bahsettiğiniz ilk yaklaşım farklı bir uyarıya neden olur: 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. Bu aynı zamanda yeni bir iş parçacığının oluşturulmasına neden olurken, yeni bir iş parçacığı async / await ile oluşturulmayabilir.
gregsdennis

Kalkmalısınız efendim!
Özkan

16

Şu anda yaptığım şey bu:

SomeAyncFunction().RunConcurrently();

Nerede RunConcurrentlytanımlanır ...

 /// <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/


1
+1. Bu, diğer yanıtların yorumlarında karşılaştırılan tüm sorunları ortadan kaldırıyor gibi görünüyor. Teşekkürler.
Grault

3
Sadece buna ihtiyacın var: public static void Forget(this Task task) { }
Shital Shah

1
orada ne demek istiyorsun @ShitalShah
Jay Wick

@ShitalShah, yalnızca ile oluşturulanlar gibi otomatik başlatma görevleriyle çalışacak async Task. Bazı görevlerin manuel olarak başlatılması gerekir.
Jonathan Allen

7

Bu uyarı ile ilgili Microsoft makalesine göre, döndürülen görevi bir değişkene atayarak sorunu çözebilirsiniz. Aşağıda Microsoft örneğinde verilen kodun bir çevirisi bulunmaktadır:

    // 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);

Bunu yapmanın ReSharper'da "Yerel değişken asla kullanılmaz" mesajıyla sonuçlanacağını unutmayın.


Evet, bu uyarıyı bastırır, ancak hayır, bu gerçekten hiçbir şeyi çözmez. Task-döndürme fonksiyonları, awaityapmamak için çok iyi bir nedeniniz yoksa -edilmelidir. Burada, görevi atmanın zaten kabul edilmiş bir async voidyöntem kullanma cevabından daha iyi olmasının hiçbir nedeni yoktur .

Ben de void yöntemini tercih ediyorum. Bu, StackOverflow'u Microsoft'tan daha akıllı hale getirir, ancak elbette bu kesin olmalıdır.
devlord

4
async voidhata işlemeyle ilgili ciddi sorunlar ortaya çıkarır ve test edilemeyen kodla sonuçlanır ( MSDN makaleme bakın ). Değişkeni kullanmak çok daha iyi olacaktır - eğer istisnaların sessizce yutulmasını istediğinizden kesinlikle eminseniz . Daha büyük olasılıkla, operasyon iki Tasks başlatmak ve sonra bir await Task.WhenAll.
Stephen Cleary

@StephenCleary Gözlemlenmeyen görevlerin istisnalarının göz ardı edilip edilmeyeceği yapılandırılabilir. Kodunuzun başka birinin projesinde kullanılması ihtimali varsa, bunun olmasına izin vermeyin. async void DoNotWait(Task t) { await t; }Tanımladığınız yöntemlerin dezavantajlarından kaçınmak için basit bir yardımcı yöntem kullanılabilir async void. (Ve Task.WhenAllOP'nin istediğini sanmıyorum , ama çok iyi olabilir.)

@hvd: Yardımcı yönteminiz onu test edilebilir kılar, ancak yine de çok zayıf istisna işleme desteği olacaktır.
Stephen Cleary

3

İşte basit bir çözüm.

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

Saygılarımızla


1

Gereksiz koda neden olan basitleştirilmiş örneğinizdir. Normalde, programın bir noktasında engelleme kaynağından getirilen verileri kullanmak istersiniz, bu nedenle verilere ulaşmanın mümkün olması için sonucu geri istersiniz.

Gerçekten programın geri kalanından tamamen izole edilmiş bir şeyiniz varsa, asenkron doğru yaklaşım olmaz. Bu görev için yeni bir iş parçacığı başlatmanız yeterli.


Ben bunu veri engelleme kaynağından getirilen kullanmak istiyorum ama bunun için beklerken arayan engellemek istemiyoruz. Zaman uyumsuz olmadan, bunu bir geri aramayı ileterek başarabilirsiniz. Örneğimde, sırayla çağrılması gereken iki eşzamansız yöntemim var ve ikinci çağrının ilk tarafından döndürülen bir değeri kullanması gerekiyor. Bu, cehennem gibi çirkinleşen iç içe geçmiş geri arama işleyicileri anlamına gelir. Okuduğum belgeler bunun özellikle asynctemizlemek için tasarlanan şey olduğunu gösteriyor ( örneğin )
Mud

@Mud: İlk eşzamansız çağrının sonucunu ikinci çağrıya parametre olarak göndermeniz yeterlidir. Bu şekilde, ikinci yöntemdeki kod hemen başlar ve istediği zaman ilk çağrının sonucunu bekleyebilir.
Guffa

Bunu yapabilirim, ancak eşzamansız olmadan bu, şu şekilde iç içe geçmiş geri aramalar anlamına gelir: MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })Bu önemsiz örnekte bile, ayrıştırılması bir kaltak. Eşzamansız ile, yazarken benim için eşdeğer kod üretiliyor result1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); Hangisi okunması çok daha kolay (her ne kadar bu yorumda çok okunabilir olmasa da!)
Mud

@Mud: Evet. Ancak ikinci eşzamansız yöntemi ilkinden hemen sonra çağırabilirsiniz, eşzamanlı aramayı beklemesi için hiçbir neden yoktur Use.
Guffa

Ne dediğini gerçekten anlamıyorum. MethodWithCallback zaman uyumsuzdur. Onları ilk aramayı beklemeden arka arkaya ararsam, ikinci arama ilk görüşmeden önce bitebilir. Ancak, ikinci için işleyicideki ilk aramanın sonucuna ihtiyacım var. Bu yüzden ilk aramayı beklemem gerekiyor.
Çamur

0

Sonucu gerçekten görmezden gelmek istiyor musun? herhangi bir beklenmedik istisnayı görmezden gelmek gibi?

Değilse şu soruya bir göz atmak isteyebilirsiniz: Ateş Et ve Unut yaklaşımı ,


0

Döndürmek için yöntem imzasını değiştirmek istemiyorsanız void(geri dönüş voidher zaman geçersiz olmalıdır ) , bunun gibi C # 7.0+ Atma özelliğini kullanabilirsiniz , bu bir değişkene atamaktan biraz daha iyidir (ve diğer birçok kaynak doğrulama araçları uyarıları):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.