Zaman uyumsuz akışlarAsync Streams
- [x] önerilir[x] Proposed
- [x] prototipi[x] Prototype
- [] Uygulama[ ] Implementation
- [] Belirtimi[ ] Specification
ÖzetSummary
C#, yineleyici yöntemleri ve zaman uyumsuz yöntemleri destekler, ancak hem Yineleyici hem de async yöntemi olan bir yöntem için desteklenmez.C# has support for iterator methods and async methods, but no support for a method that is both an iterator and an async method. Bu await
async
, yeni bir yineleyici biçiminde kullanılmasına izin vererek, bir IAsyncEnumerable<T>
IAsyncEnumerator<T>
veya yerine bir IEnumerable<T>
ya da IEnumerator<T>
gibi, IAsyncEnumerable<T>
Yeni bir ile tüketilebilir bir await foreach
değer döndürür.We should rectify this by allowing for await
to be used in a new form of async
iterator, one that returns an IAsyncEnumerable<T>
or IAsyncEnumerator<T>
rather than an IEnumerable<T>
or IEnumerator<T>
, with IAsyncEnumerable<T>
consumable in a new await foreach
. Bir IAsyncDisposable
arabirim, zaman uyumsuz temizlemeyi etkinleştirmek için de kullanılır.An IAsyncDisposable
interface is also used to enable asynchronous cleanup.
İlgili tartışmaRelated discussion
Ayrıntılı tasarımDetailed design
ArabirimlerInterfaces
IAsyncDisposableIAsyncDisposable
Çok daha fazla tartışma vardır IAsyncDisposable
(örn. https://github.com/dotnet/roslyn/issues/114) ve bunun iyi bir fikir olup olmadığı.There has been much discussion of IAsyncDisposable
(e.g. https://github.com/dotnet/roslyn/issues/114) and whether it's a good idea. Ancak, zaman uyumsuz yineleyiciler desteğini eklemek için gerekli bir kavramdır.However, it's a required concept to add in support of async iterators. finally
Bloklar await
' i içerebilir ve bu yana finally
blokların yineleyiciler elden çıkarılma kapsamında çalıştırılması gerektiğinden, zaman uyumsuz elden çıkarma gerekir.Since finally
blocks may contain await
s, and since finally
blocks need to be run as part of disposing of iterators, we need async disposal. Ayrıca, kaynakların temizlenmesi her zaman herhangi bir süre alabilir (örneğin, temizleme gerektiren), kaydını kaldırmak geri çağırmaları ve kayıt silme işleminin ne zaman tamamlandığını bilmeniz için bir yol sağlar.It's also just generally useful any time cleaning up of resources might take any period of time, e.g. closing files (requiring flushes), deregistering callbacks and providing a way to know when deregistration has completed, etc.
Çekirdek .NET kitaplıklarına aşağıdaki arabirim eklenmiştir (örn. System. Private. CoreLib/System. Runtime):The following interface is added to the core .NET libraries (e.g. System.Private.CoreLib / System.Runtime):
namespace System
{
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
}
' De olduğu gibi Dispose
, DisposeAsync
birden çok kez çağırma kabul edilebilir ve ilki, zaman uyumlu olarak tamamlanan başarılı bir görevi döndüren ve sonraki çağırmaları NOPs olarak değerlendirilmelidir ( DisposeAsync
ancak iş parçacığı açısından güvenli olmaması, ancak eşzamanlı çağrıyı desteklememe gerekir).As with Dispose
, invoking DisposeAsync
multiple times is acceptable, and subsequent invocations after the first should be treated as nops, returning a synchronously completed successful task (DisposeAsync
need not be thread-safe, though, and need not support concurrent invocation). Diğer bir deyişle, türleri hem IDisposable
hem de uygulayabilir IAsyncDisposable
ve bu durumda, aynı şekilde Invoke ve sonra da kabul edilebilir Dispose
DisposeAsync
, ancak yalnızca ilki anlamlı olmalıdır ve sonraki çağırmaları de bir NOP olmalıdır.Further, types may implement both IDisposable
and IAsyncDisposable
, and if they do, it's similarly acceptable to invoke Dispose
and then DisposeAsync
or vice versa, but only the first should be meaningful and subsequent invocations of either should be a nop. Bu nedenle, bir tür her ikisini de uygulıyorsa, tüketicilerin, Dispose
zaman uyumlu bağlamlarda ve zaman uyumsuz olarak, içeriğe göre yalnızca bir kez ve yalnızca bir kez daha ilgili yöntemi çağırması önerilir DisposeAsync
.As such, if a type does implement both, consumers are encouraged to call once and only once the more relevant method based on the context, Dispose
in synchronous contexts and DisposeAsync
in asynchronous ones.
( IAsyncDisposable
İle nasıl etkileştiğini using
ayrı bir tartışmaya bırakıyor.(I'm leaving discussion of how IAsyncDisposable
interacts with using
to a separate discussion. Ve ile nasıl etkileşime gireceğini, foreach
Bu teklifin ilerleyen kısımlarında ele alınır.)And coverage of how it interacts with foreach
is handled later in this proposal.)
Dikkate alınan alternatifler:Alternatives considered:
DisposeAsync
bir kabulCancellationToken
etme: teorik olarak, zaman uyumsuz herhangi bir şeyin iptal edilmesine, elden çıkarılmasına, işlem kapanışına, ücretsiz kaynaklardan, vb., ancak iptal edilmesi gereken iş için Temizleme işleminin hala önemli olduğu anlamına gelir.DisposeAsync
accepting aCancellationToken
: while in theory it makes sense that anything async can be canceled, disposal is about cleanup, closing things out, free'ing resources, etc., which is generally not something that should be canceled; cleanup is still important for work that's canceled.CancellationToken
Fiili çalışmanın iptal edilmesine neden olan aynı belirteç genellikle aynı şekilde geçirilseDisposeAsync
de,DisposeAsync
işin iptaliDisposeAsync
bir NOP olmasına neden olacağından çalışır.The sameCancellationToken
that caused the actual work to be canceled would typically be the same token passed toDisposeAsync
, makingDisposeAsync
worthless because cancellation of the work would causeDisposeAsync
to be a nop. Birisi elden çıkarılmayı beklerken engellenmemek isterse, sonuçta ortaya çıkan beklemeyiValueTask
veya yalnızca belirli bir süre boyunca beklemeyi önleyebilirsiniz.If someone wants to avoid being blocked waiting for disposal, they can avoid waiting on the resultingValueTask
, or wait on it only for some period of time.- Şunu döndürme: artık genel olmayan bir ve öğesinden oluşturulabildiği için, öğesinden döndürme, mevcut bir nesnenin son
DisposeAsync
zaman uyumsuz tamamlamayı temsil eden Promise olarak yeniden kullanılmasını sağlar ve zaman uyumsuz olarak tamamlandığında bir ayırmayı kaydedebilir.Task
ValueTask
IValueTaskSource
ValueTask
DisposeAsync
DisposeAsync
Task
DisposeAsync
DisposeAsync
returning aTask
: Now that a non-genericValueTask
exists and can be constructed from anIValueTaskSource
, returningValueTask
fromDisposeAsync
allows an existing object to be reused as the promise representing the eventual async completion ofDisposeAsync
, saving aTask
allocation in the case whereDisposeAsync
completes asynchronously. DisposeAsync
Birbool continueOnCapturedContext
(ConfigureAwait
) ile yapılandırma: Bu tür bir kavramınusing
,foreach
ve bunu kullanan diğer dil yapılarını, bir arabirim perspektifinden, aslında hiçbir şey yapmamak ve yapılandırılmak üzere hiçbir şey yapmamak gibi sorunlar ortaya çıkabilirawait
. uygulamasının tüketicileri,ValueTask
ancak istedikleri gibi kullanabilir.ConfiguringDisposeAsync
with abool continueOnCapturedContext
(ConfigureAwait
): While there may be issues related to how such a concept is exposed tousing
,foreach
, and other language constructs that consume this, from an interface perspective it's not actually doing anyawait
'ing and there's nothing to configure... consumers of theValueTask
can consume it however they wish.IAsyncDisposable
DevralmaIDisposable
: yalnızca bir veya diğeri kullanılması gerektiğinden, her ikisini de uygulamak için türleri zorlamak mantıklı değildir.IAsyncDisposable
inheritingIDisposable
: Since only one or the other should be used, it doesn't make sense to force types to implement both.- yerine: "zaman uyumsuz bir şey" olan ve işlemler "tamamlandı" olarak adlandırılmaları, bu nedenle türlerin ön ek olarak "Async" olması ve yöntemlerin bir sonek olarak "Async" olması gerekir.
IDisposableAsync
IAsyncDisposable
IDisposableAsync
instead ofIAsyncDisposable
: We've been following the naming that things/types are an "async something" whereas operations are "done async", so types have "Async" as a prefix and methods have "Async" as a suffix.
Iasyncenumerable/ıasyncenumeratorIAsyncEnumerable / IAsyncEnumerator
Çekirdek .NET kitaplıklarına iki arabirim eklenir:Two interfaces are added to the core .NET libraries:
namespace System.Collections.Generic
{
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> MoveNextAsync();
T Current { get; }
}
}
Tipik tüketim (ek dil özellikleri olmadan) şöyle görünür:Typical consumption (without additional language features) would look like:
IAsyncEnumerator<T> enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
Use(enumerator.Current);
}
}
finally { await enumerator.DisposeAsync(); }
Değerlendirilen atılan seçenekler:Discarded options considered:
Task<bool> MoveNextAsync(); T current { get; }
: KullanımıTask<bool>
, zaman uyumlu, başarılı çağrıları temsil etmek için önbelleğe alınmış bir görev nesnesi kullanmayı desteklerMoveNextAsync
, ancak zaman uyumsuz tamamlama için bir ayırma gerekli olmaya devam eder.Task<bool> MoveNextAsync(); T current { get; }
: UsingTask<bool>
would support using a cached task object to represent synchronous, successfulMoveNextAsync
calls, but an allocation would still be required for asynchronous completion.ValueTask<bool>
' İ döndürerek, Numaralandırıcı nesnesinin kendisi tarafından uygulanmasıIValueTaskSource<bool>
ve döndürülmek üzere kullanılması veValueTask<bool>
Bu daMoveNextAsync
önemli ölçüde azaltılan büyük kafa sayısı için de kullanılmasına olanak sağlıyoruz.By returningValueTask<bool>
, we enable the enumerator object to itself implementIValueTaskSource<bool>
and be used as the backing for theValueTask<bool>
returned fromMoveNextAsync
, which in turn allows for significantly reduced overheads.ValueTask<(bool, T)> MoveNextAsync();
: Yalnızca tüketilmekten zordur, ancak artık değişkenle birlikte olmaması anlamına gelirT
.ValueTask<(bool, T)> MoveNextAsync();
: It's not only harder to consume, but it means thatT
can no longer be covariant.ValueTask<T?> TryMoveNextAsync();
: Covariant değil.ValueTask<T?> TryMoveNextAsync();
: Not covariant.Task<T?> TryMoveNextAsync();
: Birlikte değişken değil, her çağrıda ayırmalar, vb.Task<T?> TryMoveNextAsync();
: Not covariant, allocations on every call, etc.ITask<T?> TryMoveNextAsync();
: Birlikte değişken değil, her çağrıda ayırmalar, vb.ITask<T?> TryMoveNextAsync();
: Not covariant, allocations on every call, etc.ITask<(bool,T)> TryMoveNextAsync();
: Birlikte değişken değil, her çağrıda ayırmalar, vb.ITask<(bool,T)> TryMoveNextAsync();
: Not covariant, allocations on every call, etc.Task<bool> TryMoveNextAsync(out T result);
:out
İşlem zaman uyumlu olarak döndürüldüğünde, bu işlemin daha sonra görevi zaman uyumsuz olarak tamamladığı durumlarda değil, sonucu iletmenin bir yolu olmadığı durumlarda, sonucun ayarlanmış olması gerekir.Task<bool> TryMoveNextAsync(out T result);
: Theout
result would need to be set when the operation returns synchronously, not when it asynchronously completes the task potentially sometime long in the future, at which point there'd be no way to communicate the result.IAsyncEnumerator<T>
uygulamaIAsyncDisposable
değil: bunları ayırmanızı seçebiliriz.IAsyncEnumerator<T>
not implementingIAsyncDisposable
: We could choose to separate these. Ancak, daha sonra, kodun bir Numaralandırıcı sağlamaması olasılığını karşılayamaz ve bu sayede, model tabanlı yardımcılar yazmayı zorlaştırılması gerekir.However, doing so complicates certain other areas of the proposal, as code must then be able to deal with the possibility that an enumerator doesn't provide disposal, which makes it difficult to write pattern-based helpers. Ayrıca, numaralandırıcıların elden çıkarılma ihtiyacı olan (ör. bir finally bloğuna sahip olan tüm C# zaman uyumsuz yineleyiciler, bir ağ bağlantısından verileri numaralandırma, vb.) ortakpublic ValueTask DisposeAsync() => default(ValueTask);
olacaktır.Further, it will be common for enumerators to have a need for disposal (e.g. any C# async iterator that has a finally block, most things enumerating data from a network connection, etc.), and if one doesn't, it is simple to implement the method purely aspublic ValueTask DisposeAsync() => default(ValueTask);
with minimal additional overhead.- _
IAsyncEnumerator<T> GetAsyncEnumerator()
: İptal belirteci parametresi yok._IAsyncEnumerator<T> GetAsyncEnumerator()
: No cancellation token parameter.
Uygulanabilir alternatif:Viable alternative:
namespace System.Collections.Generic
{
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator();
}
public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
ValueTask<bool> WaitForNextAsync();
T TryGetNext(out bool success);
}
}
TryGetNext
, zaman uyumlu olarak kullanılabilir oldukları sürece tek bir arabirim çağrısıyla öğeleri tüketmek için bir iç döngüde kullanılır.TryGetNext
is used in an inner loop to consume items with a single interface call as long as they're available synchronously. Bir sonraki öğe zaman uyumlu olarak alınamadıysa, false değerini döndürür ve false döndürdüğünde bir çağıran, daha sonra WaitForNextAsync
bir sonraki öğenin kullanılabilir olmasını beklemek ya da başka bir öğe olmadığı saptanmalıdır.When the next item can't be retrieved synchronously, it returns false, and any time it returns false, a caller must subsequently invoke WaitForNextAsync
to either wait for the next item to be available or to determine that there will never be another item. Tipik tüketim (ek dil özellikleri olmadan) şöyle görünür:Typical consumption (without additional language features) would look like:
IAsyncEnumerator<T> enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.WaitForNextAsync())
{
while (true)
{
int item = enumerator.TryGetNext(out bool success);
if (!success) break;
Use(item);
}
}
}
finally { await enumerator.DisposeAsync(); }
Bunun avantajı iki kat, bir küçük ve bir büyük:The advantage of this is two-fold, one minor and one major:
- İkincil: bir Numaralandırıcının birden çok tüketicileri desteklemesini sağlar.Minor: Allows for an enumerator to support multiple consumers. Bir Numaralandırıcının birden çok eşzamanlı tüketicilerini desteklemesi için değerli senaryolar olabilir.There may be scenarios where it's valuable for an enumerator to support multiple concurrent consumers. Bu,
MoveNextAsync
Current
bir uygulamanın kullanım atomik hale getirme yapabilmeleri için ne zaman ve ayrı olduğunda elde edilebilir.That can't be achieved whenMoveNextAsync
andCurrent
are separate such that an implementation can't make their usage atomic. Buna karşılık bu yaklaşım,TryGetNext
Numaralandırıcı ileri doğru göndermeyi ve sonraki öğeyi almayı destekleyen tek bir yöntem sağlar. bu nedenle, Numaralandırıcı istenirse, Numaralandırıcının kararlılık 'i etkinleştirebilir.In contrast, this approach provides a single methodTryGetNext
that supports pushing the enumerator forward and getting the next item, so the enumerator can enable atomicity if desired. Ancak, bu tür senaryolar, her tüketiciye paylaşılan bir numaralandırıcıdan kendi Numaralandırıcı vererek da etkinleştirilebilir.However, it's likely that such scenarios could also be enabled by giving each consumer its own enumerator from a shared enumerable. Ayrıca, her Numaralandırıcı için çok sayıda büyük kafa gerektirmeyen büyük/küçük harfe basit olmayan büyük kafa ekler ve bu da bir arabirimin tüketicisinin genellikle bu herhangi bir şekilde dayanmadığı anlamına gelir.Further, we don't want to enforce that every enumerator support concurrent usage, as that would add non-trivial overheads to the majority case that doesn't require it, which means a consumer of the interface generally couldn't rely on this any way. - Birincil: performans.Major: Performance.
MoveNextAsync
/Current
Yaklaşım, işlem başına iki arabirim çağrısı gerektirir, ancak için en iyi durum,WaitForNextAsync
/TryGetNext
her birTryGetNext
işlem için yalnızca bir arabirim çağrısı olması gibi, ile sıkı bir iç döngüyü etkinleştirerek, ile çok sayıda yinelemenin eşzamanlı olarak tamammıdır.TheMoveNextAsync
/Current
approach requires two interface calls per operation, whereas the best case forWaitForNextAsync
/TryGetNext
is that most iterations complete synchronously, enabling a tight inner loop withTryGetNext
, such that we only have one interface call per operation. Bu durum, arabirimin, hesaplamayı ayırt ettiği durumlarda ölçülebilir bir etkiye sahip olabilir.This can have a measurable impact in situations where the interface calls dominate the computation.
Bununla birlikte, bu, el ile kullanılırken büyük ölçüde artan karmaşıklık ve bunları kullanırken hatalara yönelik daha fazla şans dahil olmak üzere, önemsiz olmayan küçük yanlar vardır.However, there are non-trivial downsides, including significantly increased complexity when consuming these manually, and an increased chance of introducing bugs when using them. Ayrıca, performans avantajları mikro kıyaslamalar halinde gösterilirken, gerçek kullanımın büyük çoğunluğunda ne kadar etkili olduğunu düşünmedik.And while the performance benefits show up in microbenchmarks, we don't believe they'll be impactful in the vast majority of real usage. Bunlar ise, hafif bir şekilde ikinci bir arabirim kümesi tanıtıyoruz.If it turns out they are, we can introduce a second set of interfaces in a light-up fashion.
Değerlendirilen atılan seçenekler:Discarded options considered:
ValueTask<bool> WaitForNextAsync(); bool TryGetNext(out T result);
:out
parametreler birlikte değişken olamaz.ValueTask<bool> WaitForNextAsync(); bool TryGetNext(out T result);
:out
parameters can't be covariant. Burada, bu durum büyük olasılıkla başvuru türü sonuçları için bir çalışma zamanı yazma engeli olan küçük bir etkisi (genel olarak TRY düzeniyle ilgili bir sorun) de vardır.There's also a small impact here (an issue with the try pattern in general) that this likely incurs a runtime write barrier for reference type results.
İptalCancellation
İptali desteklemeye yönelik birkaç olası yaklaşım vardır:There are several possible approaches to supporting cancellation:
IAsyncEnumerable<T>
/IAsyncEnumerator<T>
iptal-belirsiz:CancellationToken
herhangi bir yerde görünmez.IAsyncEnumerable<T>
/IAsyncEnumerator<T>
are cancellation-agnostic:CancellationToken
doesn't appear anywhere. İptal etme işlemi,CancellationToken
uygun şekilde sayılabilen ve/veya Numaralandırıcının mantıklı bir şekilde (örneğin, bir yineleyici çağrılırken), yineleyiciCancellationToken
yöntemine bir bağımsız değişken olarak geçirilerek ve yineleyicinin gövdesinde kullanıldığı gibi başka herhangi bir parametre ile yapıldığı zaman elde edilir.Cancellation is achieved by logically baking theCancellationToken
into the enumerable and/or enumerator in whatever manner is appropriate, e.g. when calling an iterator, passing theCancellationToken
as an argument to the iterator method and using it in the body of the iterator, as is done with any other parameter.IAsyncEnumerator<T>.GetAsyncEnumerator(CancellationToken)
: Bir ' aCancellationToken
GetAsyncEnumerator
ve sonraki işlemleri buna göre geçitirsiniz,MoveNextAsync
ancak bunu yapabilir.IAsyncEnumerator<T>.GetAsyncEnumerator(CancellationToken)
: You pass aCancellationToken
toGetAsyncEnumerator
, and subsequentMoveNextAsync
operations respect it however it can.IAsyncEnumerator<T>.MoveNextAsync(CancellationToken)
: BirCancellationToken
tek her çağrıya geçiş yapabilirsinizMoveNextAsync
.IAsyncEnumerator<T>.MoveNextAsync(CancellationToken)
: You pass aCancellationToken
to each individualMoveNextAsync
call.- 1 && 2: her ikisi de her ikisi de
CancellationToken
numaralandırılabilir/Numaralandırıcıya ekleme ve 'CancellationToken
ye geçişGetAsyncEnumerator
1 && 2: You both embedCancellationToken
s into your enumerable/enumerator and passCancellationToken
s intoGetAsyncEnumerator
. - 1 && 3: her ikisi de her ikisi de
CancellationToken
numaralandırılabilir/Numaralandırıcıya ekleme ve 'CancellationToken
ye geçişMoveNextAsync
1 && 3: You both embedCancellationToken
s into your enumerable/enumerator and passCancellationToken
s intoMoveNextAsync
.
Tamamen teorik bir perspektiften (5) en sağlam bir deyişle, (a), MoveNextAsync
CancellationToken
iptallerin en hassas denetimini sağladığından, (b) CancellationToken
yalnızca Yineleyicilerde bağımsız değişken olarak geçirilebilecek, rastgele türlere gömülü olan diğer herhangi bir tür, vb. olabilir.From a purely theoretical perspective, (5) is the most robust, in that (a) MoveNextAsync
accepting a CancellationToken
enables the most fine-grained control over what's canceled, and (b) CancellationToken
is just any other type that can passed as an argument into iterators, embedded in arbitrary types, etc.
Ancak, bu yaklaşımda birden fazla sorun vardır:However, there are multiple problems with that approach:
CancellationToken
Yineleyicinin gövdesinde nasıl bir şekilde geçerGetAsyncEnumerator
?How does aCancellationToken
passed toGetAsyncEnumerator
make it into the body of the iterator?iterator
Geçirilen öğesine erişim sağlamak için, seçtiğiniz yeni bir anahtar sözcüğü kullanıma sunarız.CancellationToken
GetEnumerator
ancak, çok fazla sayıda ek makine olan b) çok birinci sınıf vatandaşlık ve c), %99 büyük/küçük harf olarak bir yineleyici çağıran ve üzerinde çağıran aynı kod olarak görünebilir ve buGetAsyncEnumerator
durumda yalnızcaCancellationToken
yöntemine bir bağımsız değişken olarak geçirebileceği anlamına gelir.We could expose a newiterator
keyword that you could dot off of to get access to theCancellationToken
passed toGetEnumerator
, but a) that's a lot of additional machinery, b) we're making it a very first-class citizen, and c) the 99% case would seem to be the same code both calling an iterator and callingGetAsyncEnumerator
on it, in which case it can just pass theCancellationToken
as an argument into the method.CancellationToken
MoveNextAsync
Yöntemin gövdesine ulaşmak için nasıl geçirilir?How does aCancellationToken
passed toMoveNextAsync
get into the body of the method? Bu, yerel bir nesnenin kullanımına sunulmamış gibi daha da kötüsüiterator
, değeri await genelinde değişebilir, bu, belirteçle kaydedilen tüm kodların,MoveNextAsync
bir yineleyicinin bir yineleyici veya bir geliştirici tarafından el ile uygulanıp uygulanmadığı bağımsız olarak, her çağrıda kayıt yaptırması ve kaydının silinmesinin gerekli olduğu anlamına gelir.This is even worse, as if it's exposed off of aniterator
local object, its value could change across awaits, which means any code that registered with the token would need to unregister from it prior to awaits and then re-register after; it's also potentially quite expensive to need to do such registering and unregistering in everyMoveNextAsync
call, regardless of whether implemented by the compiler in an iterator or by a developer manually.- Geliştirici bir döngüyü nasıl iptal edebilir
foreach
?How does a developer cancel aforeach
loop? Bu işlemCancellationToken
, bir numaralandırılabilir/Numaralandırıcı vererek yapılır, sonra da bir),foreach
bunları ilk sınıf vatandaşları olarak başlatan numaralandırıcılara yönelik Ayrıca, numaralandırıcıların (ÖRNEĞIN, LINQ yöntemleri) veya b 'nin etrafında oluşturulmuş bir ekosistem hakkında düşünmeye başlamanız gerekirCancellationToken
. birWithCancellation
Genişletme yönteminin,IAsyncEnumerable<T>
belirtilen belirteci depolayacağından ve ardındanGetAsyncEnumerator
GetAsyncEnumerator
döndürülen yapıda çağrıldığında (Bu belirteç gözardı edildiğinde) sarmalanmış numaralandırıcılara geçirerek, ' ı yine de NUMARALANDIRILAMAZ.If it's done by giving aCancellationToken
to an enumerable/enumerator, then either a) we need to supportforeach
'ing over enumerators, which raises them to being first-class citizens, and now you need to start thinking about an ecosystem built up around enumerators (e.g. LINQ methods) or b) we need to embed theCancellationToken
in the enumerable anyway by having someWithCancellation
extension method off ofIAsyncEnumerable<T>
that would store the provided token and then pass it into the wrapped enumerable'sGetAsyncEnumerator
when theGetAsyncEnumerator
on the returned struct is invoked (ignoring that token). Ya da yalnızca foreach gövdesinde bulunan ' yi kullanabilirsinizCancellationToken
.Or, you can just use theCancellationToken
you have in the body of the foreach. - Sorgu öngörülerinin desteklenme biçimi destekleniyorsa,
CancellationToken
her bir yan tümcesine nasıl sağlanacakGetEnumerator
veyaMoveNextAsync
geçirilecek?If/when query comprehensions are supported, how would theCancellationToken
supplied toGetEnumerator
orMoveNextAsync
be passed into each clause? En kolay yol, yan tümce için yalnızca bunu yakalamak için kullanılır; Bu noktada hangi belirtecin iletildiğiniGetAsyncEnumerator
/MoveNextAsync
yok sayılır.The easiest way would simply be for the clause to capture it, at which point whatever token is passed toGetAsyncEnumerator
/MoveNextAsync
is ignored.
Bu belgenin önceki bir sürümü önerilir (1), ancak karşılaştık (4).An earlier version of this document recommended (1), but we since switched to (4).
(1) ile ilgili iki ana sorun:The two main problems with (1):
- üreticileri of cancellenebilir numaralar, bazı ortak uygulama ve derleyicinin yalnızca zaman uyumsuz-yineleyiciler desteğini bir yöntem uygulamak için kullanabilirler
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken)
.producers of cancellable enumerables have to implement some boilerplate, and can only leverage the compiler's support for async-iterators to implement aIAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken)
method. - birçok üreticileri
CancellationToken
, bunun yerine yalnızca zaman uyumsuz imzaya bir parametre eklemeyi tercih edilebilir, bu da tüketicilerin bir tür verildiğinde istediği iptal belirtecini geçmesini engellerIAsyncEnumerable
.it is likely that many producers would be tempted to just add aCancellationToken
parameter to their async-enumerable signature instead, which will prevent consumers from passing the cancellation token they want when they are given anIAsyncEnumerable
type.
İki ana tüketim senaryosu vardır:There are two main consumption scenarios:
await foreach (var i in GetData(token)) ...
Tüketici, zaman uyumsuz-yineleyici yöntemini çağırıyorsa,await foreach (var i in GetData(token)) ...
where the consumer calls the async-iterator method,await foreach (var i in givenIAsyncEnumerable.WithCancellation(token)) ...
tüketicinin belirli bir örnekle ilgilenen konumuIAsyncEnumerable
.await foreach (var i in givenIAsyncEnumerable.WithCancellation(token)) ...
where the consumer deals with a givenIAsyncEnumerable
instance.
Her iki senaryoyu da hem üreticileri hem de Async-Streams kullanıcıları için uygun bir şekilde desteklemeye yönelik makul bir uzlaşma olduğunu fark ettik. Bu, zaman uyumsuz-yineleyici yönteminde özel olarak açıklamalı bir parametre kullanmaktır.We find that a reasonable compromise to support both scenarios in a way that is convenient for both producers and consumers of async-streams is to use a specially annotated parameter in the async-iterator method. [EnumeratorCancellation]
Özniteliği bu amaçla kullanılır.The [EnumeratorCancellation]
attribute is used for this purpose. Bu özniteliğin bir parametreye yerleştirilmesi derleyiciye bir belirteç geçirildiğini GetAsyncEnumerator
, bu belirtecin parametre için başlangıçta geçirilen değer yerine kullanılması gerektiğini belirtir.Placing this attribute on a parameter tells the compiler that if a token is passed to the GetAsyncEnumerator
method, that token should be used instead of the value originally passed for the parameter.
Göz önünde bulundurun IAsyncEnumerable<int> GetData([EnumeratorCancellation] CancellationToken token = default)
.Consider IAsyncEnumerable<int> GetData([EnumeratorCancellation] CancellationToken token = default)
. Bu yöntemin uygulayıcısı Yöntem gövdesinde parametresini yalnızca kullanabilir.The implementer of this method can simply use the parameter in the method body. Tüketici, yukarıdaki tüketim desenlerini kullanabilir:The consumer can use either consumption patterns above:
- kullanırsanız
GetData(token)
, belirteç zaman uyumsuz numaralandırılabilir olarak kaydedilir ve yineleme içinde kullanılacaktır,if you useGetData(token)
, then the token is saved into the async-enumerable and will be used in iteration, - kullanırsanız
givenIAsyncEnumerable.WithCancellation(token)
, öğesine geçirilen belirteç,GetAsyncEnumerator
zaman uyumsuz numaralandırılabilir olarak kaydedilen tüm belirteçlerinin yerini alır.if you usegivenIAsyncEnumerable.WithCancellation(token)
, then the token passed toGetAsyncEnumerator
will supersede any token saved in the async-enumerable.
foreachforeach
foreach``IAsyncEnumerable<T>
, için mevcut desteğinin yanı sıra desteklemek için de genişletilebilir IEnumerable<T>
.foreach
will be augmented to support IAsyncEnumerable<T>
in addition to its existing support for IEnumerable<T>
. Ayrıca, IAsyncEnumerable<T>
ilgili Üyeler herkese açık olarak ortaya çıkarılabildiği zaman bir model olarak eşdeğerini destekleyecektir, ancak ayırmayı önlemek için yapı tabanlı uzantıların yanı sıra, ve dönüş türü olarak alternatif awaitables kullanımını etkinleştirmek için MoveNextAsync
DisposeAsync
.And it will support the equivalent of IAsyncEnumerable<T>
as a pattern if the relevant members are exposed publicly, falling back to using the interface directly if not, in order to enable struct-based extensions that avoid allocating as well as using alternative awaitables as the return type of MoveNextAsync
and DisposeAsync
.
SyntaxSyntax
Sözdizimini kullanarak:Using the syntax:
foreach (var i in enumerable)
C#, zaman enumerable
uyumsuz numaralar için Ilgili API 'leri kullanıma sunsa bile, zaman uyumlu olmayan bir şekilde işlemeye devam edecektir (Bu, stili kullanıma sunma veya arabirimini uygulama), yalnızca zaman uyumlu API 'leri göz önünde bulunduracaktır.C# will continue to treat enumerable
as a synchronous enumerable, such that even if it exposes the relevant APIs for async enumerables (exposing the pattern or implementing the interface), it will only consider the synchronous APIs.
Yerine zorlamak için foreach
yalnızca zaman uyumsuz API 'leri göz önünde bulundurun, await
aşağıdaki gibi eklenir:To force foreach
to instead only consider the asynchronous APIs, await
is inserted as follows:
await foreach (var i in enumerable)
Async veya Sync API 'Lerini kullanmayı destekleyecek bir sözdizimi sağlanmaz; geliştirici, kullanılan sözdizimine göre seçim yapmanız gerekir.No syntax would be provided that would support using either the async or the sync APIs; the developer must choose based on the syntax used.
Değerlendirilen atılan seçenekler:Discarded options considered:
foreach (var i in await enumerable)
: Bu, zaten geçerli bir söz dizimi ve anlamının değiştirilmesi bir son değişiklik olacaktır.foreach (var i in await enumerable)
: This is already valid syntax, and changing its meaning would be a breaking change. Bunun anlamı,await
enumerable
zaman uyumlu olarak yinelenebilir bir şey geri alır ve bunu zaman uyumlu olarak yineleyebilirsiniz.This means toawait
theenumerable
, get back something synchronously iterable from it, and then synchronously iterate through that.foreach (var i await in enumerable)
,foreach (var await i in enumerable)
,foreach (await var i in enumerable)
: Bu, bir sonraki öğeyi bekletireceğiz, ancak foreach ' te yer alan başka bir bekler vardır, özellikle de Numaralandırılabilir bir ise,IAsyncDisposable
await
zaman uyumsuz elden çıkarma işlemi yapılır.foreach (var i await in enumerable)
,foreach (var await i in enumerable)
,foreach (await var i in enumerable)
: These all suggest that we're awaiting the next item, but there are other awaits involved in foreach, in particular if the enumerable is anIAsyncDisposable
, we will beawait
'ing its async disposal. Bu await, her bir öğe için değil, Foreach 'in kapsamı olarak kullanılır ve bu nedenleawait
anahtar sözcüğüforeach
düzeyinde olur.That await is as the scope of the foreach rather than for each individual element, and thus theawait
keyword deserves to be at theforeach
level. Ayrıca, ile ilişkili olması,foreach
foreach
farklı bir terim (örn. bir "await foreach") ile birlikte anlatmak için bir yol sunar.Further, having it associated with theforeach
gives us a way to describe theforeach
with a different term, e.g. a "await foreach". Ancak daha da önemlisi, söz dizimi ile aynı zamanda sözdizimi dikkate alındığında bir değer vardırforeach
using
ve bu sayede birbirleriyle tutarlı kalırlar veusing (await ...)
zaten geçerli bir sözdizimi vardır.But more importantly, there's value in consideringforeach
syntax at the same time asusing
syntax, so that they remain consistent with each other, andusing (await ...)
is already valid syntax.foreach await (var i in enumerable)
Yine de göz önünde bulundurun:Still to consider:
foreach
Bugün, bir Numaralandırıcı boyunca yineleme desteklemez.foreach
today does not support iterating through an enumerator. Daha fazla bilgi sahibi olmak için daha yaygın olacağını umdukIAsyncEnumerator<T>
ve bu nedenleawait foreach
hem hem de ile desteklemek içinIAsyncEnumerable<T>
IAsyncEnumerator<T>
.We expect it will be more common to haveIAsyncEnumerator<T>
s handed around, and thus it's tempting to supportawait foreach
with bothIAsyncEnumerable<T>
andIAsyncEnumerator<T>
. Ancak bu tür destek eklendikten sonra,IAsyncEnumerator<T>
birinci sınıf bir vatandaşlık olup olmadığı ve numaralar 'e ek olarak Numaralandırıcılar üzerinde çalışan kombinatör 'nin aşırı yüklemelerinin olması gerekip gerekmediğini tanıtılsın mı?But once we add such support, it introduces the question of whetherIAsyncEnumerator<T>
is a first-class citizen, and whether we need to have overloads of combinators that operate on enumerators in addition to enumerables? Yöntemleri numaralar yerine Numaralandırıcılar döndürecek şekilde teşvik etmek istiyor musunuz?Do we want to encourage methods to return enumerators rather than enumerables? Bunu tartışmak için devam etmemiz gerekir.We should continue to discuss this. Bu uygulamayı desteklemek istemediğimiz için karar verdiğimiz bir uzantı yöntemi tanıtmak istiyoruzpublic static IAsyncEnumerable<T> AsEnumerable<T>(this IAsyncEnumerator<T> enumerator);
foreach
.If we decide we don't want to support it, we might want to introduce an extension methodpublic static IAsyncEnumerable<T> AsEnumerable<T>(this IAsyncEnumerator<T> enumerator);
that would allow an enumerator to still beforeach
'd. Bunu desteklemek isteymemiz gerektiğine karar verdiğimiz için,await foreach
Numaralandırıcının çağrılmasından sorumlu olup olmadığınaDisposeAsync
ve yanıtın büyük olasılıkla "Hayır, elden çıkarılma sırasında denetimi mi ele alınmalıdırGetEnumerator
."If we decide we do want to support it, we'll need to also decide on whether theawait foreach
would be responsible for callingDisposeAsync
on the enumerator, and the answer is likely "no, control over disposal should be handled by whoever calledGetEnumerator
."
Model tabanlı derlemePattern-based Compilation
Derleyici, varsa model tabanlı API 'lere bağlanır, bu da arabirimi kullanarak bunları tercih eder (model, örnek yöntemleriyle veya uzantı yöntemleriyle karşılanacaktır).The compiler will bind to the pattern-based APIs if they exist, preferring those over using the interface (the pattern may be satisfied with instance methods or extension methods). Düzenin gereksinimleri şunlardır:The requirements for the pattern are:
- Numaralandırılabilir
GetAsyncEnumerator
değer, bağımsız değişken olmadan çağrılabilecek ve ilgili düzene uyan bir Numaralandırıcı döndüren bir yöntemi kullanıma sunmalıdır.The enumerable must expose aGetAsyncEnumerator
method that may be called with no arguments and that returns an enumerator that meets the relevant pattern. - Numaralandırıcı
MoveNextAsync
bağımsız değişken olmadan çağrılabilecek bir yöntemi kullanıma sunmalı ve bu,await
Ed veGetResult()
döndüren bir şey döndürüyorbool
.The enumerator must expose aMoveNextAsync
method that may be called with no arguments and that returns something which may beawait
ed and whoseGetResult()
returns abool
. - Numaralandırıcı Ayrıca,
Current
alıcısıT
numaralandırılan veri türünü temsil eden bir özelliği kullanıma sunmalıdır.The enumerator must also exposeCurrent
property whose getter returns aT
representing the kind of data being enumerated. - Numaralandırıcı isteğe bağlı olarak,
DisposeAsync
bağımsız değişken olmadan çağrılabilecek ve geri döndürülen ve döndüren bir şeyi döndüren bir yöntemi kullanıma sunmayabilirawait
GetResult()
void
.The enumerator may optionally expose aDisposeAsync
method that may be invoked with no arguments and that returns something that can beawait
ed and whoseGetResult()
returnsvoid
.
Bu kod:This code:
var enumerable = ...;
await foreach (T item in enumerable)
{
...
}
, öğesinin eşdeğerine çevrilir:is translated to the equivalent of:
var enumerable = ...;
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
...
}
}
finally
{
await enumerator.DisposeAsync(); // omitted, along with the try/finally, if the enumerator doesn't expose DisposeAsync
}
Yinelenebilir tür doğru düzende kullanıma sunulmazsa, arabirimler kullanılacaktır.If the iterated type doesn't expose the right pattern, the interfaces will be used.
ConfigureAwaitConfigureAwait
Bu kalıp tabanlı derleme, ConfigureAwait
bir genişletme yöntemi aracılığıyla await 'in tümünde kullanılmasına izin verir ConfigureAwait
:This pattern-based compilation will allow ConfigureAwait
to be used on all of the awaits, via a ConfigureAwait
extension method:
await foreach (T item in enumerable.ConfigureAwait(false))
{
...
}
Bunun yanı sıra, System.Threading.Tasks.Extensions.dll için de .NET 'a eklediğimiz türler temel alınacaktır:This will be based on types we'll add to .NET as well, likely to System.Threading.Tasks.Extensions.dll:
// Approximate implementation, omitting arg validation and the like
namespace System.Threading.Tasks
{
public static class AsyncEnumerableExtensions
{
public static ConfiguredAsyncEnumerable<T> ConfigureAwait<T>(this IAsyncEnumerable<T> enumerable, bool continueOnCapturedContext) =>
new ConfiguredAsyncEnumerable<T>(enumerable, continueOnCapturedContext);
public struct ConfiguredAsyncEnumerable<T>
{
private readonly IAsyncEnumerable<T> _enumerable;
private readonly bool _continueOnCapturedContext;
internal ConfiguredAsyncEnumerable(IAsyncEnumerable<T> enumerable, bool continueOnCapturedContext)
{
_enumerable = enumerable;
_continueOnCapturedContext = continueOnCapturedContext;
}
public ConfiguredAsyncEnumerator<T> GetAsyncEnumerator() =>
new ConfiguredAsyncEnumerator<T>(_enumerable.GetAsyncEnumerator(), _continueOnCapturedContext);
public struct Enumerator
{
private readonly IAsyncEnumerator<T> _enumerator;
private readonly bool _continueOnCapturedContext;
internal Enumerator(IAsyncEnumerator<T> enumerator, bool continueOnCapturedContext)
{
_enumerator = enumerator;
_continueOnCapturedContext = continueOnCapturedContext;
}
public ConfiguredValueTaskAwaitable<bool> MoveNextAsync() =>
_enumerator.MoveNextAsync().ConfigureAwait(_continueOnCapturedContext);
public T Current => _enumerator.Current;
public ConfiguredValueTaskAwaitable DisposeAsync() =>
_enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext);
}
}
}
}
Bu yaklaşımın ConfigureAwait
, model tabanlı numaralar birlikte kullanılmasına izin kullanılmayacağını unutmayın, ancak daha sonra, ConfigureAwait
yalnızca üzerinde bir uzantı olarak sunulmasıdır Task
/ Task<T>
/ ValueTask
/ ValueTask<T>
ve yalnızca görevlere uygulandığında (görevin devamlılık desteğiyle uygulanan bir davranışı denetler) yalnızca anlamlı hale getirilebileceği için, bu durum, zaman içinde yapılan nesnelerin görev olmadığı bir model kullanırken anlamlı değildir.Note that this approach will not enable ConfigureAwait
to be used with pattern-based enumerables, but then again it's already the case that the ConfigureAwait
is only exposed as an extension on Task
/Task<T>
/ValueTask
/ValueTask<T>
and can't be applied to arbitrary awaitable things, as it only makes sense when applied to Tasks (it controls a behavior implemented in Task's continuation support), and thus doesn't make sense when using a pattern where the awaitable things may not be tasks. Daha fazla işlem döndüren herkes, bu Gelişmiş senaryolarda kendi özel davranışlarını sağlayabilir.Anyone returning awaitable things can provide their own custom behavior in such advanced scenarios.
(Kapsam veya derleme düzeyi çözümü desteklemenizin bir yolu ile karşılaştığımız için ConfigureAwait
Bu gerekli değildir.)(If we can come up with some way to support a scope- or assembly-level ConfigureAwait
solution, then this won't be necessary.)
Zaman uyumsuz yineleyicilerAsync Iterators
Dil/derleyici, IAsyncEnumerable<T>
IAsyncEnumerator<T>
bunları tüketmenin yanı sıra s ve s oluşturmayı de destekleyecektir.The language / compiler will support producing IAsyncEnumerable<T>
s and IAsyncEnumerator<T>
s in addition to consuming them. Günümüzde dil, şunun gibi bir yineleyici yazmayı destekler:Today the language supports writing an iterator like:
static IEnumerable<int> MyIterator()
{
try
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(1000);
yield return i;
}
}
finally
{
Thread.Sleep(200);
Console.WriteLine("finally");
}
}
Ancak await
Bu yineleyicilerin gövdesinde kullanılamaz.but await
can't be used in the body of these iterators. Bu desteği ekleyeceğiz.We will add that support.
SyntaxSyntax
Yineleyiciler için mevcut dil desteği, her birinin içerip içermediğini temel alarak yöntemin Yineleyici yapısını algılar yield
.The existing language support for iterators infers the iterator nature of the method based on whether it contains any yield
s. Aynı zaman uyumsuz yineleyiciler için de doğru olacaktır.The same will be true for async iterators. Bu zaman uyumsuz yineleyiciler, imzaya eklenerek, zaman uyumlu yineleyiciler tarafından parçalanacaktır ve async
aynı zamanda IAsyncEnumerable<T>
dönüş türüne ya da daha sonra da sahip olmalıdır IAsyncEnumerator<T>
.Such async iterators will be demarcated and differentiated from synchronous iterators via adding async
to the signature, and must then also have either IAsyncEnumerable<T>
or IAsyncEnumerator<T>
as its return type. Örneğin, yukarıdaki örnek aşağıdaki şekilde zaman uyumsuz bir yineleyici olarak yazılabilir:For example, the above example could be written as an async iterator as follows:
static async IAsyncEnumerable<int> MyIterator()
{
try
{
for (int i = 0; i < 100; i++)
{
await Task.Delay(1000);
yield return i;
}
}
finally
{
await Task.Delay(200);
Console.WriteLine("finally");
}
}
Dikkate alınan alternatifler:Alternatives considered:
async
İmzada kullanmayın: Using,async
Bu bağlamda geçerli olup olmadığını tespit etmek üzere kullandığı için teknik olarak derleyici tarafından gerektirilirawait
.Not usingasync
in the signature: Usingasync
is likely technically required by the compiler, as it uses it to determine whetherawait
is valid in that context. Ancak gerekli olmasa bile,await
yalnızca olarak işaretlenen yöntemlerde kullanılabilirasync
ve tutarlılığın tutulması önemli görünmektedir.But even if it's not required, we've established thatawait
may only be used in methods marked asasync
, and it seems important to keep the consistency.- İçin
IAsyncEnumerable<T>
özel oluşturucular etkinleştiriliyor: Bu, geleceğe bakabiliriz ancak makineler karmaşıktır ve zaman uyumlu ortaklarınıza yönelik olarak desteklenmez.Enabling custom builders forIAsyncEnumerable<T>
: That's something we could look at for the future, but the machinery is complicated and we don't support that for the synchronous counterparts. - İmzada bir
iterator
anahtar sözcük vardır: zaman uyumsuz yineleyiciler İmzada kullanılırasync iterator
veyield
yalnızcaasync
dahil edilen metotlarda kullanılabiliriterator
;iterator
daha sonra zaman uyumlu yineleyiciler üzerinde isteğe bağlı olarak yapılır.Having aniterator
keyword in the signature: Async iterators would useasync iterator
in the signature, andyield
could only be used inasync
methods that includediterator
;iterator
would then be made optional on synchronous iterators. Bakış açısına bağlı olarak, bu, izin verilen, yöntemin imzasına göre çok açık hale getirme avantajına sahiptiryield
ve yönteminIAsyncEnumerable<T>
kodun kullanılıp kullanılmayacağını temel alarak derleyicinin üretimi yerine tür örnekleri döndürmesinin gerekmediği anlamına geliryield
.Depending on your perspective, this has the benefit of making it very clear by the signature of the method whetheryield
is allowed and whether the method is actually meant to return instances of typeIAsyncEnumerable<T>
rather than the compiler manufacturing one based on whether the code usesyield
or not. Ancak zaman uyumlu yineleyiciler farklıdır, bu, bir tane gerektirmez ve bu yapılamaz.But it is different from synchronous iterators, which don't and can't be made to require one. Ayrıca, bazı geliştiriciler ek söz dizimini beğenmez.Plus some developers don't like the extra syntax. Bunu sıfırdan tasarladığımızda, bu gerekli olacaktır, ancak bu noktada, zaman uyumsuz yineleyiciler eşitleme yineleyiciler için kapalı tutulması çok daha fazla değer vardır.If we were designing it from scratch, we'd probably make this required, but at this point there's much more value in keeping async iterators close to sync iterators.
LINQLINQ
Sınıf üzerinde yöntemlerin System.Linq.Enumerable
, hepsi de tüm çalışan, IEnumerable<T>
Bu kabul etme IEnumerable<T>
, bazıları IEnumerable<T>
ve birçok do.. 200There are over ~200 overloads of methods on the System.Linq.Enumerable
class, all of which work in terms of IEnumerable<T>
; some of these accept IEnumerable<T>
, some of them produce IEnumerable<T>
, and many do both. İçin LINQ desteğinin eklenmesi IAsyncEnumerable<T>
, başka bir ~ 200 için bu aşırı yüklemelerin tümünü çoğaltmak büyük olasılıkla kuyruğa alabilir.Adding LINQ support for IAsyncEnumerable<T>
would likely entail duplicating all of these overloads for it, for another ~200. Zaman IAsyncEnumerator<T>
uyumsuz dünyada, zaman uyumlu dünyada olan tek başına bir varlık olarak daha yaygın olduğundan IEnumerator<T>
, ile birlikte çalışan başka bir ~ 200 aşırı yüklemesi gerekebilir IAsyncEnumerator<T>
.And since IAsyncEnumerator<T>
is likely to be more common as a standalone entity in the asynchronous world than IEnumerator<T>
is in the synchronous world, we could potentially need another ~200 overloads that work with IAsyncEnumerator<T>
. Ayrıca, çok sayıda aşırı yükleme, koşullara göre (örn.) oluşur Where
Func<T, bool>
ve IAsyncEnumerable<T>
hem zaman uyumlu hem de zaman uyumsuz koşullara (örn. Buna ek olarak) yönelik tabanlı aşırı yüklemeler olması istenebilir Func<T, ValueTask<bool>>
Func<T, bool>
.Plus, a large number of the overloads deal with predicates (e.g. Where
that takes a Func<T, bool>
), and it may be desirable to have IAsyncEnumerable<T>
-based overloads that deal with both synchronous and asynchronous predicates (e.g. Func<T, ValueTask<bool>>
in addition to Func<T, bool>
). Bu, şu an ~ 400 yeni aşırı yükleme için geçerli olmasa da, kabaca bir hesaplama, bu da bir ~ 200 aşırı yüklemesi, yani toplam ~ 600 yeni yöntem için geçerli olacaktır.While this isn't applicable to all of the now ~400 new overloads, a rough calculation is that it'd be applicable to half, which means another ~200 overloads, for a total of ~600 new methods.
Bu, kademelendirme sayıda API 'dir ve etkileşimli uzantılar (x) gibi uzantı kitaplıkları kabul edildiğinde daha da mümkün olur.That is a staggering number of APIs, with the potential for even more when extension libraries like Interactive Extensions (Ix) are considered. Ancak x, bunlardan birçoğu için zaten bir uygulama içeriyor ve bu çalışmayı yinelemek için harika bir neden görünmüyor; Bunun yerine, topluluğun x 'i iyileştirmesine yardımcı olur ve geliştiricilerin LINQ ile birlikte kullanmak istedikleri zaman için önerilir IAsyncEnumerable<T>
.But Ix already has an implementation of many of these, and there doesn't seem to be a great reason to duplicate that work; we should instead help the community improve Ix and recommend it for when developers want to use LINQ with IAsyncEnumerable<T>
.
Sorgu anlama söz dizimi sorunu da vardır.There is also the issue of query comprehension syntax. Sorgu anlama 'nın model tabanlı doğası, bazı işleçlerle "yalnızca çalışma" yapmasına izin verir, örneğin, x aşağıdaki yöntemleri sağlar:The pattern-based nature of query comprehensions would allow them to "just work" with some operators, e.g. if Ix provides the following methods:
public static IAsyncEnumerable<TResult> Select<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, TResult> func);
public static IAsyncEnumerable<T> Where(this IAsyncEnumerable<T> source, Func<T, bool> func);
Bu C# kodu, "yalnızca çalışacaktır" olacaktır:then this C# code will "just work":
IAsyncEnumerable<int> enumerable = ...;
IAsyncEnumerable<int> result = from item in enumerable
where item % 2 == 0
select item * 2;
Ancak, yan tümcelerinde kullanmayı destekleyen sorgu kavrama sözdizimi yoktur await
, yani x eklenirse, örneğin:However, there is no query comprehension syntax that supports using await
in the clauses, so if Ix added, for example:
public static IAsyncEnumerable<TResult> Select<TSource, TResult>(this IAsyncEnumerable<TSource> source, Func<TSource, ValueTask<TResult>> func);
Bu durumda "yalnızca çalışma" olur:then this would "just work":
IAsyncEnumerable<string> result = from url in urls
where item % 2 == 0
select SomeAsyncMethod(item);
async ValueTask<int> SomeAsyncMethod(int item)
{
await Task.Yield();
return item * 2;
}
Ancak await
yan tümce içinde satır içi ile yazılması mümkün olmayacaktır select
.but there'd be no way to write it with the await
inline in the select
clause. Ayrı bir çaba olarak, dile ifade eklemeye görünebiliriz async { ... }
. bu noktada, sorgu anlama 'da kullanılmasına izin verebilir ve yukarıdaki şu şekilde yazılabilir:As a separate effort, we could look into adding async { ... }
expressions to the language, at which point we could allow them to be used in query comprehensions and the above could instead be written as:
IAsyncEnumerable<int> result = from item in enumerable
where item % 2 == 0
select async
{
await Task.Yield();
return item * 2;
};
ya da await
' nin destekleyici olması gibi ifadelerde doğrudan kullanılmasına olanak tanır async from
.or to enabling await
to be used directly in expressions, such as by supporting async from
. Bununla birlikte, burada bir tasarımın bir yolu veya diğerini, bu özellik kümesinin geri kalanını etkiler ve bu, özellikle de bir çok yüksek değerli şey olmak üzere hemen yatırım yapacak bir şey değildir.However, it's unlikely a design here would impact the rest of the feature set one way or the other, and this isn't a particularly high-value thing to invest in right now, so the proposal is to do nothing additional here right now.
Diğer zaman uyumsuz çerçevelerle tümleştirmeIntegration with other asynchronous frameworks
IObservable<T>
Ve diğer zaman uyumsuz çerçevelerle (ör. reaktif akışlar) tümleştirme, dil düzeyi yerine kitaplık düzeyinde yapılır.Integration with IObservable<T>
and other asynchronous frameworks (e.g. reactive streams) would be done at the library level rather than at the language level. Örneğin, bir ' deki tüm veriler, IAsyncEnumerator<T>
Numaralandırıcının üzerine bir şekilde yayımlanabilir IObserver<T>
await foreach
ve OnNext
verileri gözlemci 'e alarak bir AsObservable<T>
genişletme yöntemi mümkündür.For example, all of the data from an IAsyncEnumerator<T>
can be published to an IObserver<T>
simply by await foreach
'ing over the enumerator and OnNext
'ing the data to the observer, so an AsObservable<T>
extension method is possible. IObservable<T>
Bir içinde kullanmak await foreach
için verilerin arabelleğe alınması gerekir (önceki öğe işlenmeye devam ederken başka bir öğenin itilmesi durumunda), ancak bu tür bir gönderim bağdaştırıcısı, IObservable<T>
ile arasında çekilmeyi sağlamak için kolayca uygulanabilir IAsyncEnumerator<T>
.Consuming an IObservable<T>
in a await foreach
requires buffering the data (in case another item is pushed while the previous item is still being processing), but such a push-pull adapter can easily be implemented to enable an IObservable<T>
to be pulled from with an IAsyncEnumerator<T>
. Benzerlerini. RX/x, bu tür uygulamaların prototiplerini zaten sağlıyor ve https://github.com/dotnet/corefx/tree/master/src/System.Threading.Channels farklı türlerde arabelleğe alma veri yapıları sağlar.Etc. Rx/Ix already provide prototypes of such implementations, and libraries like https://github.com/dotnet/corefx/tree/master/src/System.Threading.Channels provide various kinds of buffering data structures. Bu aşamada dilin dahil olmaması gerekir.The language need not be involved at this stage.