Neden C # 'da ValueTask <T> yerine Görev <T> kullanılır?


168

C # 7.0'dan itibaren zaman uyumsuz yöntemler ValueTask <T> döndürebilir. Açıklama, önbelleğe alınmış bir sonuca sahip olduğumuzda veya senkronize kodla zaman uyumsuzluğu taklit ettiğimizde kullanılması gerektiğini söylüyor. Ancak yine de ValueTask'ı her zaman veya async / await'in neden başlangıçtan itibaren bir değer türü ile inşa edilmediğini bilmiyorum. ValueTask ne zaman işi yapamaz?


7
Bunun yararları ile ilgisi şüpheli ValueTask<T>olan işlemler için somutlaşmıyor (tahsisleri açısından) aslında (bu durumda çünkü asenkron ValueTask<T>hala yığın ayırma gerekecek). Task<T>Kütüphaneler içinde başka desteklerin de olması sorunu var .
Jon Skeet

3
@JonSkeet varolan kütüphaneler bir sorun var ama bu, Görev'in başından beri ValueTask olması gerektiği sorusunu yalvarır? Gerçek asenkron öğeler için kullanıldığında faydalar olmayabilir, ancak zararlı mıdır?
17'de Stilgar



3
@JoelMueller arsa kalınlaşıyor :)
Stilgar

Yanıtlar:


245

Gönderen API docs (vurgu eklenmiştir):

Onların operasyonları sonucu eş zamanlı satışa sunulacak olasılıkla işte o zaman Yöntemler bu değer türü örneği döndürebilir ve yöntem bu yüzden sık sık yeni tahsis bedeli olduğunu çağrılan olması bekleniyor zaman Task<TResult>her arama için engelleyici olacaktır.

Bir ValueTask<TResult>yerine kullanmak için ödünler vardır Task<TResult>. Örneğin, a ValueTask<TResult>, başarılı sonucun eşzamanlı olarak kullanılabilmesi durumunda bir tahsisin önlenmesine yardımcı olabilirken, aynı zamanda iki alan içerirken Task<TResult>, bir referans türü olarak a tek bir alandır. Bu, bir yöntem çağrısının, biri yerine iki alan değerinde veri döndürmesi anlamına gelir ve bu da kopyalanacak daha fazla veri demektir. Ayrıca, bunlardan birini döndüren bir asyncyöntem bir yöntem içinde beklenirse, bu yöntemin durum makinesinin, asynctek bir başvuru yerine iki alan olan yapıyı depolaması gerektiğinden daha büyük olacağı anlamına gelir.

Ayrıca, üzerinden bir asenkron işlemin sonucunu tüketen dışındaki kullanımlar için await, ValueTask<TResult>aslında çevirmek daha tahsisleri yol açabilir daha dolambaçlı bir programlama modeli, yol açabilir. Örneğin, Task<TResult>ortak bir sonuç olarak önbelleğe alınmış bir göreve sahip bir yöntem veya bir ValueTask<TResult>. Sonucun tüketici bir olarak kullanmak istiyorsa Task<TResult>gibi yöntemlerde ile bu tür kullanım için, Task.WhenAllve Task.WhenAny, ValueTask<TResult>ilk olarak bir dönüştürülebilir gerekir Task<TResult>kullanarak AsTask, bir önbelleğe alınmış ise kaçınılması olurdu ödenek hangi yol açar Task<TResult>kullanılmıştı ilk başta.

Bu nedenle, herhangi bir eşzamansız yöntem için varsayılan seçim bir Taskveya döndürmek olmalıdır Task<TResult>. Sadece performans analizi buna değerse, ValueTask<TResult>bunun yerine kullanılmalıdır Task<TResult>.


7
@MattThomas: Tek bir Taskayırma kaydeder (bu günlerde küçük ve ucuzdur), ancak arayanın mevcut tahsisini büyütme ve dönüş değerinin boyutunu iki katına çıkarma (kayıt tahsisini etkileme) pahasına . Arabelleğe alınmış bir okuma senaryosu için açık bir seçim olsa da, varsayılan olarak tüm arabirimlere uygulamak önerdiğim bir şey değildir.
Stephen Cleary

1
Sağ, ya Taskda ValueTask(veya Task.FromResult) senkronize bir dönüş türü olarak kullanılabilir . Ama eşzamanlı olmasını beklediğiniz birValueTask şey varsa hala değer (heh) var . klasik bir örnek olmak. Ben inanıyorum performans gerçekten önemli nerede muhtemelen de ASP.NET çekirdeğinde kullanılan yeni "kanallar" (düşük seviyeli bayt akışları) için ağırlıklı olarak yaratılmıştır. ReadByteAsync ValueTask
Stephen Cleary

1
Oh biliyorum, lol, sadece bu belirli yoruma eklemek için bir şey olup olmadığını merak ediyordum;)
julealgon

2
Does bu PR ValueTask tercih etmek dengeyi geçiş? (ref: blog.marcgravell.com/2019/08/… )
stuartd

2
@stuartd: Şimdilik Task<T>varsayılan olarak kullanmanızı tavsiye ederim . Bunun nedeni, çoğu geliştiricinin etrafındaki kısıtlamalara aşina olmamasıdır ValueTask<T>(özellikle "bir kez tüket" kuralı ve "engelleme yok" kuralı). Bununla birlikte ValueTask<T>, ekibinizdeki tüm geliştiriciler iyi ise , o zaman takım düzeyinde bir tercih kılavuzu öneririm ValueTask<T>.
Stephen Cleary

104

Ancak hala her zaman ValueTask kullanma ile ilgili sorunun ne olduğunu anlamıyorum

Yapı türleri ücretsiz değildir. Bir referansın boyutundan büyük olan yapıları kopyalamak, bir referansı kopyalamaktan daha yavaş olabilir. Bir referanstan daha büyük yapıların depolanması, bir referansın depolanmasından daha fazla bellek gerektirir. Bir referansın kaydı alınabildiğinde 64 bit'ten büyük yapılara kayıt yapılamayabilir. Daha düşük toplama basıncının faydaları maliyetleri aşmayabilir.

Performans problemlerine mühendislik disiplini ile yaklaşılmalıdır. Hedeflerinizi belirleyin, hedeflere karşı ilerlemenizi ölçün ve ardından hedeflerinize ulaşılmazsa programda nasıl değişiklik yapılacağına karar verin, değişikliklerinizin gerçekten iyileştirmeler olduğundan emin olmak için yol boyunca ölçün.

neden zaman uyumsuzluk / beklemenin başından beri bir değer türüyle oluşturulmadığı.

awaitTask<T>tür zaten mevcut olduktan çok sonra C # 'a eklendi . Zaten mevcut olduğunda yeni bir tür icat etmek biraz sapkın olurdu. Ve await2012'de sevk edilene yerleşmeden önce birçok tasarım yinelemesi yaptı. Mükemmel, iyinin düşmanıdır; mevcut altyapı ile iyi çalışan bir çözüm göndermek daha iyidir ve daha sonra kullanıcı talebi varsa, daha sonra iyileştirmeler sağlayın.

Ayrıca, kullanıcı tarafından sağlanan türlerin derleyici tarafından üretilen bir yöntemin çıktısı olmasına izin vermenin yeni özelliğinin önemli ölçüde risk ve test yükü eklediğine dikkat çekiyorum. Geri verebileceğiniz tek şey geçersiz veya bir görev olduğunda, test ekibinin kesinlikle çılgın bir türün döndürüldüğü herhangi bir senaryoyu düşünmesi gerekmez. Bir derleyici aracı test programları insanların yazma muhtemeldir değil sadece ne endam, ama ne programlardır mümkün biz derleyici tüm yasal programlar, sadece bütün mantıklı programları derlemek istiyoruz, çünkü yazmak için. Bu pahalı.

Birisi ValueTask'ın işi ne zaman başaramayacağını açıklayabilir mi?

İşin amacı geliştirilmiş performans. Performansı ölçülebilir ve önemli ölçüde iyileştirmezse işi yapmaz . Bunun olacağının garantisi yoktur.


23

ValueTask<T>bir alt küme değil Task<T>, bir üst kümedir .

ValueTask<T>bir T ve a'nın ayrımcı bir birleşimidir Task<T>, bu da ReadAsync<T>onu sahip olduğu bir T değerini eşzamanlı olarak döndürmek için tahsissiz hale getirir (kullanmanın aksine Task.FromResult<T>, bir Task<T>örnek tahsis etmesi gerekir ). ValueTask<T>beklenmektedir, bu nedenle çoğu örnek tüketimi a ile ayırt edilemez olacaktır Task<T>.

Bir yapı olan ValueTask, API tutarlılığından ödün vermeden eşzamanlı olarak çalıştıklarında bellek ayırmayan zaman uyumsuz yöntemlerin yazılmasını sağlar. Görev döndürme yöntemiyle bir arayüze sahip olduğunuzu düşünün. Bu arabirimi uygulayan her sınıf, eşzamanlı olarak yürütülseler bile bir Görev döndürmelidir (umarım Görev.FromResult'u kullanarak). Tabii ki arayüzde 2 farklı yöntem, senkronize ve senkronize olmayan bir yöntem olabilir ancak bu, “eşzamansız senkronizasyon” ve “eşzamanlı senkronize olmayan” durumlardan kaçınmak için 2 farklı uygulama gerektirir.

Böylece, her biri için başka bir şekilde özdeş bir yöntem yazmak yerine, eşzamansız veya eşzamanlı bir yöntem yazmanıza izin verir. Kullandığınız herhangi bir yerde kullanabilirsiniz, Task<T>ancak çoğu zaman hiçbir şey eklemez.

Şey, bir şey ekler: Arayana yöntemin aslında ek işlevler kullandığına dair ima edilen bir söz ekler ValueTask<T>. Ben şahsen, arayan kişiye mümkün olduğunca bilgi veren parametre ve dönüş türlerini seçmeyi tercih ederim. IList<T>Numaralandırma bir sayı sağlayamıyorsa geri dönmeyin; IEnumerable<T>Eğer geri dönmeyin. Tüketicileriniz hangi yöntemlerinizin eşzamanlı olarak çağırılabileceğini ve hangilerinin eşzamanlı olarak çağrılamayacağını bilmek için herhangi bir belge aramak zorunda kalmamalıdır.

Gelecekteki tasarım değişikliklerini orada ikna edici bir argüman olarak görmüyorum. Aksine: Bazen bir yöntem anlambilim değiştirirse, o gerektiğini kendisine tüm aramalar buna göre güncellenir kadar yapı bölünürler. Bu istenmeyen olarak kabul edilirse (ve inan bana, yapıyı kırmama arzusuna sempati duyuyorsam), arayüz versiyonlamasını düşünün.

Esasen güçlü yazmanın amacı budur.

Mağazanızda zaman uyumsuz yöntemler tasarlayan bazı programcılar bilinçli kararlar alamıyorsa, daha az deneyimli programcıların her birine kıdemli bir mentor atamak ve haftalık kod incelemesi yapmak yardımcı olabilir. Yanlış tahmin ederse, bunun neden farklı yapılması gerektiğini açıklayın. Üst düzey çocuklar için yükü, ancak gençleri sadece derin uca atmak ve takip etmeleri için keyfi bir kural vermek yerine çok daha hızlı hızlandıracak.

Yöntemi yazan adam eşzamanlı olarak çağrılabilir mi bilmiyorsa , Dünya'da kim yapar ?!

Zaman uyumsuz yöntemler yazan birçok deneyimsiz programcıya sahipseniz, aynı insanlar onları da çağırıyor mu? Hangilerinin asenkron olarak adlandırılması güvenli olduğunu kendileri anlamaya uygunlar mı yoksa bunları nasıl adlandırdıklarına benzer şekilde keyfi bir kural uygulamaya başlayacaklar mı?

Buradaki sorun geri dönüş türleriniz değil, programcılar için hazır olmadıkları rollere sokuluyor. Bunun bir sebepten dolayı olması gerekirdi, bu yüzden düzeltmenin önemsiz olacağına eminim. Bunu tanımlamak kesinlikle bir çözüm değildir. Ancak derleyiciyi aşarak sorunu gizlemek için bir yol aramak da bir çözüm değildir.


4
Dönen bir yöntem görürseniz ValueTask<T>, yöntem aslında ValueTask<T>ekleyen işlevselliği kullandığından yöntemi yazan adam bunu yaptığını varsayalım . Tüm yöntemlerinizin aynı dönüş türüne sahip olmanın neden gerekli olduğunu düşündüğünüzü anlamıyorum. Oradaki amaç nedir?
15ee8f99-57ff-4f92-890c-b56153

2
Hedef 1, uygulamanın değiştirilmesine izin vermektir. Şimdi sonucu önbelleğe almıyorsam ancak daha sonra önbellek kodunu eklersem ne olur? Hedef 2, tüm üyelerin ValueTask'ın ne zaman yararlı olduğunu anlamadığı bir ekip için açık bir rehber olmaktır. Kullanırken herhangi bir zarar yoksa her zaman yapın.
17'de Stilgar

6
Benim görüşüme göre, neredeyse tüm yöntemlerinizin geri dönmesi gibi, objectçünkü belki bir gün onların intyerine geri dönmelerini istersiniz string.
15ee8f99-57ff-4f92-890c-b56153

3
Belki de "neden nesne yerine bir ip döndürürsünüz?" ValueTask'ı her yerde kullanmamanın nedenleri çok daha ince görünüyor.
17'de Stilgar

3
@Stilgar Söyleyeceğim bir şey: İnce problemler sizi belirgin olanlardan daha fazla endişelendirmelidir.
15ee8f99-57ff-4f92-890c-b56153

20

Net Core 2.1'de bazı değişiklikler var . .Net core 2.1'den başlayarak ValueTask sadece senkronize edilmiş işlemleri değil, aynı zamanda tamamlanmış senkronizasyonu da temsil edebilir. Ayrıca jenerik olmayan bir ValueTasktip alırız .

Sorunuzla ilgili Stephen Toub yorumunu bırakacağım :

Hala rehberliği resmileştirmemiz gerekiyor, ancak genel API yüzey alanı için böyle bir şey olacağını umuyorum:

  • Görev en çok kullanılabilirliği sağlar.

  • ValueTask, performans optimizasyonu için en fazla seçeneği sunar.

  • Başkalarının geçersiz kılacağı bir arabirim / sanal yöntem yazıyorsanız, ValueTask doğru varsayılan seçimdir.

  • API'nın, ayırmaların önemli olacağı sıcak yollarda kullanılmasını bekliyorsanız, ValueTask iyi bir seçimdir.

  • Aksi takdirde, performansın kritik olmadığı yerlerde, daha iyi garantiler ve kullanılabilirlik sağladığı için Görev'i varsayılan olarak kullanın.

Uygulama perspektifinden bakıldığında, döndürülen ValueTask örneklerinin çoğu yine de Görev tarafından desteklenir.

Özellik sadece .net çekirdeğinde kullanılamaz 2.1. System.Threading.Tasks.Extensions paketi ile kullanabilirsiniz .


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.