"Bu eşzamansız yöntem 'bekleme' operatörlerinden yoksun ve eşzamanlı olarak çalışacak" uyarısı hakkında endişelenmeli miyim


93

Bazı eşzamansız yöntemleri ortaya çıkaran bir arabirimim var. Daha spesifik olarak, Görev veya Görev <T> döndüren tanımlanmış yöntemlere sahiptir. Async / await anahtar sözcüklerini kullanıyorum.

Bu arayüzü uygulama sürecindeyim. Ancak, bu yöntemlerin bazılarında bu uygulamanın bekleyeceği hiçbir şey yoktur. Bu nedenle derleyici uyarısını alıyorum "Bu zaman uyumsuz yöntem 'await' işleçlerinden yoksun ve eşzamanlı olarak çalışacak ..."

Neden hatayı aldığımı anlıyorum ama bu bağlamda onlar hakkında bir şey yapmam gerekip gerekmediğini merak ediyorum. Derleyici uyarılarını görmezden gelmek yanlış geliyor.

Task.Run'da bekleyerek düzeltebileceğimi biliyorum, ancak bu, yalnızca birkaç ucuz işlem yapan bir yöntem için yanlış geliyor. Ayrıca, yürütmeye gereksiz ek yük katacak gibi görünüyor, ancak bunun zaten orada olup olmadığından emin değilim çünkü async anahtar sözcüğü mevcut.

Uyarıları görmezden mi gelmeliyim yoksa bunun etrafında çalışmanın görmediğim bir yolu var mı?


2
Özelliklere bağlı olacak. Bu işlemlerin eşzamanlı olarak yapılmasını istediğinizden gerçekten emin misiniz? Eşzamanlı olarak gerçekleştirilmesini istiyorsanız, neden yöntem olarak işaretlenir async?
Servy

11
asyncAnahtar kelimeyi kaldırmanız yeterlidir. Yine de Taskkullanarak iade edebilirsiniz Task.FromResult.
Michael Liu

1
@BenVoigt Google, OP'nin zaten bilmemesi durumunda, bununla ilgili bilgilerle doludur.
Servy

1
@BenVoigt Michael Liu bu ipucunu zaten vermemiş miydi? Kullanın Task.FromResult.

1
@hvd: Bu daha sonra yorumunda düzenlendi.
Ben Voigt

Yanıtlar:


144

Zaman uyumsuz anahtar sözcük yalnızca bir yöntemin uygulama ayrıntısıdır; yöntem imzasının bir parçası değildir. Belirli bir yöntem uygulaması veya geçersiz kılmanın bekleyecek hiçbir şeyi yoksa, zaman uyumsuz anahtar sözcüğünü atlayın ve Task.FromResult <TResult> kullanarak tamamlanmış bir görevi döndürün :

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }

Senin yöntem dönerse Görevi yerine Görev <TResult> , o zaman herhangi bir tür ve değer tamamlanmış bir görevi dönebilir. Task.FromResult(0)popüler bir seçim gibi görünüyor:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult(0);          //
}                                       //    }

Veya .NET Framework 4.6'dan itibaren Task.CompletedTask'ı döndürebilirsiniz :

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.CompletedTask;          //
}                                       //    }

Teşekkürler Sanırım eksik olan şey, sizin söylediğiniz gibi async anahtar kelimesine sahip olmakla aynı olan gerçek bir görevi döndürmek yerine, tamamlanmış bir Görev oluşturma konseptiydi. Şimdi açık görünüyor ama ben görmüyordum!
dannykay1710

1
Görev, bu amaç için Task.Empty satırları boyunca statik bir üye ile yapılabilir. Niyet biraz daha net olacaktı ve asla ihtiyaç duyulmayan bir sıfırı döndüren tüm bu görevli Görevleri düşünmek bana acı veriyor.
Rupert Rawnsley

await Task.FromResult(0)? Nasıl olur await Task.Yield()?
Sushi271

1
@ Sushi271: Hayır, bir sivil de asyncyöntemin, sen dönmek Task.FromResult(0) onu bekleyen yerine.
Michael Liu

1
Aslında HAYIR, eşzamansız sadece bir uygulama detayı değildir, etrafında bilinmesi gereken birçok detay vardır :). Hangi parçanın eşzamanlı olarak çalıştığının, hangi bölümün eşzamansız olarak çalıştığının, mevcut senkronizasyon bağlamının ne olduğunun ve sadece kayıt için, perdelerin arkasında durum makinesi olmadığı için Görevler her zaman biraz daha hızlıdır :)
ipavlu

16

Bazı "eşzamansız" işlemlerin eşzamanlı olarak tamamlanması, ancak yine de çok biçimlilik uğruna eşzamansız çağrı modeline uyması tamamen mantıklıdır.

Bunun gerçek hayattan bir örneği, OS I / O API'leridir. Bazı cihazlarda eşzamansız ve çakışan çağrılar her zaman satır içi tamamlanır (örneğin, paylaşılan bellek kullanılarak uygulanan bir boruya yazma). Ancak arka planda devam eden çok parçalı işlemlerle aynı arayüzü uygularlar.


4

Michael Liu, uyarıdan nasıl kaçınabileceğinizle ilgili sorunuzu iyi yanıtladı: Task.FromResult'u döndürerek.

Sorunuzun "Uyarı konusunda endişelenmeli miyim" kısmına cevap vereceğim.

Cevap Evet!

Bunun nedeni Task, awaitoperatör olmadan zaman uyumsuz bir yöntemin içinde dönen bir yöntemi çağırdığınızda uyarının sık sık ortaya çıkmasıdır . Önceki işlemi beklemeden Entity Framework'te bir işlemi çağırdığım için oluşan bir eşzamanlılık hatasını düzelttim.

Derleyici uyarılarından kaçınmak için kodunuzu titizlikle yazabilirseniz, o zaman bir uyarı olduğunda, ağrılı bir başparmak gibi öne çıkacaktır. Birkaç saat hata ayıklamaktan kaçınabilirdim.


5
Bu cevap çok yanlış. awaitNedeni şu: bir yerde yöntemin içinde en az bir tane olabilir (CS1998 olmayacak) ancak bu, senkronizasyondan yoksun olacak başka bir asnyc yöntemi çağrısı olmayacağı anlamına gelmez (kullanarak awaitveya başka herhangi biri). Şimdi birisi yanlışlıkla senkronizasyonu kaçırmadığınızdan nasıl emin olacağınızı öğrenmek isterse, başka bir uyarıyı - CS4014 - görmezden gelmediğinizden emin olun. Hatta bunu hata olarak tehdit etmeyi bile tavsiye ederim.
Victor Yarema

3

Çok geç olabilir, ancak yararlı bir araştırma olabilir:

Derlenmiş kodun ( IL ) iç yapısı hakkında :

 public static async Task<int> GetTestData()
    {
        return 12;
    }

IL'de olur:

.method private hidebysig static class [mscorlib]System.Threading.Tasks.Task`1<int32> 
        GetTestData() cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 28 55 73 61 67 65 4C 69 62 72 61 72 79 2E   // ..(UsageLibrary.
                                                                                                                                     53 74 61 72 74 54 79 70 65 2B 3C 47 65 74 54 65   // StartType+<GetTe
                                                                                                                                     73 74 44 61 74 61 3E 64 5F 5F 31 00 00 )          // stData>d__1..
  .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       52 (0x34)
  .maxstack  2
  .locals init ([0] class UsageLibrary.StartType/'<GetTestData>d__1' V_0,
           [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> V_1)
  IL_0000:  newobj     instance void UsageLibrary.StartType/'<GetTestData>d__1'::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldloc.0
  IL_0007:  call       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
  IL_000c:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.m1
  IL_0013:  stfld      int32 UsageLibrary.StartType/'<GetTestData>d__1'::'<>1__state'
  IL_0018:  ldloc.0
  IL_0019:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_001e:  stloc.1
  IL_001f:  ldloca.s   V_1
  IL_0021:  ldloca.s   V_0
  IL_0023:  call       instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class UsageLibrary.StartType/'<GetTestData>d__1'>(!!0&)
  IL_0028:  ldloc.0
  IL_0029:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
  IL_002e:  call       instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
  IL_0033:  ret
} // end of method StartType::GetTestData

Ve eşzamansız ve görev yöntemi olmadan:

 public static int GetTestData()
        {
            return 12;
        }

şu hale gelir:

.method private hidebysig static int32  GetTestData() cil managed
{
  // Code size       8 (0x8)
  .maxstack  1
  .locals init ([0] int32 V_0)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   12
  IL_0003:  stloc.0
  IL_0004:  br.s       IL_0006
  IL_0006:  ldloc.0
  IL_0007:  ret
} // end of method StartType::GetTestData

Bu yöntemler arasındaki büyük farkı görebileceğiniz gibi. Await inside async yöntemini kullanmıyorsanız ve async yöntemini kullanmayı umursamıyorsanız (örneğin API çağrısı veya olay işleyicisi), iyi fikir onu normal eşitleme yöntemine dönüştürür (uygulama performansınızı korur).

Güncellenmiş:

Ayrıca microsoft docs https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth adresinden ek bilgiler de mevcuttur :

zaman uyumsuz yöntemlerin vücutlarında bir await anahtar kelimesi olması gerekir, aksi takdirde asla sonuç vermezler! Bunu akılda tutmak önemlidir. Bir zaman uyumsuz yöntemin gövdesinde await kullanılmazsa, C # derleyicisi bir uyarı oluşturur, ancak kod, normal bir yöntemmiş gibi derlenir ve çalışır. Zaman uyumsuz yöntem için C # derleyicisi tarafından oluşturulan durum makinesi hiçbir şey başaramayacağından, bunun da inanılmaz derecede verimsiz olacağını unutmayın.


2
Ek async/awaitolarak, CPU'ya bağlı tek bir işlemin gerçekçi olmayan örneğine dayandırdığınız için , kullanımı ile ilgili nihai sonucunuz büyük ölçüde basitleştirilmiştir. Taskuygun şekilde kullanılan ipliklerin iyileştirilmiş bir uygulama performansı ve yanıt dolayı eş zamanlı görevleri (yani paralel) ve daha iyi yönetmek ve kullanım için izin verdiğinde s
MickyD

Bu, bu yazıda söylediğim gibi basitleştirilmiş bir örnek. Ayrıca, yöntemlerin her iki sürümünü (zaman uyumsuz ve normal) kullanarak mümkün olan api ve olay işleyicilerine yapılan isteklerden de bahsetmiştim. Ayrıca PO, içeride beklemeden zaman uyumsuz yöntemleri kullanmayı söyledi. Yazım bununla ilgiliydi ama doğru kullanmakla ilgili değildi Tasks. Yazının tamamını okumamak ve hızlı bir şekilde sonuçlara varmamak üzücü bir hikaye.
Oleg Bondarenko

1
Geri dönen bir yöntem int(sizin durumunuzda olduğu Taskgibi ) ile OP tarafından tartışıldığı gibi dönen bir yöntem arasında bir fark vardır . Oku onun görevini ve kabul cevabı tekrar yerine bizzat şeyleri alarak. Cevabınız bu durumda yardımcı olmuyor. awaitİçinde olan veya olmayan bir yöntem arasındaki farkı gösterme zahmetine bile girmiyorsunuz. Şimdi bunu yapmış olsaydın, olumlu oy almaya değerdi
MickyD

Sanırım zaman uyumsuz yöntem ile api veya olay işleyicileri ile çağrılan normal yöntemler arasındaki farkı gerçekten anlamıyorsunuz. Yazımda özellikle belirtildi. Özür dilerim onu yine kaçırıyorsun .
Oleg Bondarenko

1

Geri dönerken istisna davranışı hakkında not Task.FromResult

Burada işaretlenmiş ve işaretlenmemiş yöntemler arasındaki istisna işlemedeki farkı gösteren küçük bir demo var async.

public Task<string> GetToken1WithoutAsync() => throw new Exception("Ex1!");

// Warning: This async method lacks 'await' operators and will run synchronously. Consider ...
public async Task<string> GetToken2WithAsync() => throw new Exception("Ex2!");  

public string GetToken3Throws() => throw new Exception("Ex3!");
public async Task<string> GetToken3WithAsync() => await Task.Run(GetToken3Throws);

public async Task<string> GetToken4WithAsync() { throw new Exception("Ex4!"); return await Task.FromResult("X");} 


public static async Task Main(string[] args)
{
    var p = new Program();

    try { var task1 = p.GetToken1WithoutAsync(); } 
    catch( Exception ) { Console.WriteLine("Throws before await.");};

    var task2 = p.GetToken2WithAsync(); // Does not throw;
    try { var token2 = await task2; } 
    catch( Exception ) { Console.WriteLine("Throws on await.");};

    var task3 = p.GetToken3WithAsync(); // Does not throw;
    try { var token3 = await task3; } 
    catch( Exception ) { Console.WriteLine("Throws on await.");};

    var task4 = p.GetToken4WithAsync(); // Does not throw;
    try { var token4 = await task4; } 
    catch( Exception ) { Console.WriteLine("Throws on await.");};
}
// .NETCoreApp,Version=v3.0
Throws before await.
Throws on await.
Throws on await.
Throws on await.

( Arabirim tarafından zaman uyumsuz Task <T> gerektiğinde, derleyici uyarısı olmadan dönüş değişkeni nasıl alınır için cevabımın çapraz gönderimi )

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.