Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Zaman uyumsuz işlemlerle çalışmak için Görev Tabanlı Zaman Uyumsuz Deseni (TAP) kullandığınızda, engelleme olmadan beklemeyi başarmak için geri çağırmaları kullanabilirsiniz. Bu desen, görevler için Task.ContinueWith gibi yöntemleri kullanır. Dil tabanlı zaman uyumsuz destek, normal denetim akışında zaman uyumsuz işlemlerin beklenmesine izin vererek geri çağırmaları gizler ve derleyici tarafından oluşturulan kod aynı API düzeyinde destek sağlar.
Await ile Yürütmeyi Askıya Alma
C# dilinde await anahtar sözcüğünü ve Visual Basic'teki Await İşlecini kullanarak Task ve Task<TResult> nesnelerini zaman uyumsuz şekilde bekleyebilirsiniz. Bir Task beklediğinizde, await ifadesi void türündedir. Bir Task<TResult> beklediğinizde, await ifadesi TResult türündedir. Bir await ifadenin, zaman uyumsuz bir yöntemin gövdesinde yer alması gerekir. (Bu dil özellikleri .NET Framework 4.5'te kullanıma sunulmuştur.)
Perde arkasında, await işlevi bir devam mekanizması kullanarak göreve bir geri çağırma yükler. Bu geriçağırım, askıya alma noktasında asenkron yöntemi sürdürür. Zaman uyumsuz yöntem sürdürülürken, beklenen işlem başarıyla tamamlandıysa ve bir `Task<TResult>` ise, `TResult` döndürülür. Beklenen Task veya Task<TResult>Canceled durumunda sona erdiyse, bir OperationCanceledException özel durum oluşturulur.
Task veya Task<TResult> beklenen unsurların Faulted durumunda sona ermesi durumunda, hatayı tetikleyen istisna fırlatılır. Bir Task birden fazla istisna sonucu hata yapabilir, ancak bu istisnalardan yalnızca biri aktarılır. Ancak, Task.Exception özelliği tüm hataları içeren bir AggregateException özel durum döndürür.
Askıya alma sırasında zaman uyumsuz yöntemi yürüten iş parçacığıyla bir eşitleme bağlamı (SynchronizationContext nesne) ilişkilendirildiyse SynchronizationContext.Current (örneğin, özellik değilse null), zaman uyumsuz yöntem bağlamın Post yöntemini kullanarak aynı eşitleme bağlamında devam eder. Aksi takdirde, askıya alma sırasında geçerli olan görev zamanlayıcıya (TaskScheduler nesne) dayanır. Bu genellikle iş parçacığı havuzunu hedefleyen varsayılan görev zamanlayıcıdır (TaskScheduler.Default ). Bu görev zamanlayıcı, beklenen zaman uyumsuz işlemin tamamlandığı noktadan devam edip etmeyeceğini veya devamın zamanlanması gerekip gerekmediğini belirler. Varsayılan zamanlayıcı genellikle devamın beklenen işlemin tamamlandığı iş parçacığında çalışmasına izin verir.
Zaman uyumsuz bir yöntemi çağırdığınızda, henüz tamamlanmamış bir beklenebilir örnekteki ilk await ifadesine kadar işlevin gövdesini zaman uyumlu bir şekilde yürütür ve bu noktada kontrol çağrıyı yapana geri döner. Zaman uyumsuz yöntem döndürmezse void, devam eden hesaplamayı temsil eden bir Task veya Task<TResult> nesnesi döndürür. Geçersiz olmayan bir zaman uyumsuz yöntemde, bir dönüş deyimiyle karşılaşıldığında veya yöntem gövdesinin sonuna gelindiğinde, görev son durumunda tamamlanır RanToCompletion. Eğer işlenmeyen bir özel durum, kontrolün zaman uyumsuz yöntemin gövdesinden çıkmasına neden olursa, görev Faulted durumunda biter. Eğer bu bir OperationCanceledException özel durumuysa, görev bunun yerine Canceled durumunda tamamlanır. Bu şekilde sonuç veya özel durum sonunda yayımlanır.
Bu davranışın birkaç önemli varyasyonu vardır. Performans nedenleriyle, bir görev görev beklenilene kadar zaten tamamlanmışsa, denetim yüklenmez ve işlev yürütülmeye devam eder. Ayrıca özgün bağlama geri dönmek her zaman istenen davranış değildir ve değiştirilebilir; bu davranış sonraki bölümde daha ayrıntılı olarak açıklanmıştır.
Yield ve ConfigureAwait ile Askıya Alma ve Yeniden Başlatmayı Yapılandırma
Çeşitli yöntemler, zaman uyumsuz bir yöntemin yürütülmesi üzerinde daha fazla denetim sağlar. Örneğin, eşzamansız bir yönteme bir verim noktası eklemek için Task.Yield yöntemini kullanabilirsiniz.
public class Task : …
{
public static YieldAwaitable Yield();
…
}
Bu yöntem, geçerli bağlama zaman uyumsuz bir şekilde geri bildirim gönderme veya zamanlama yapmakla eşdeğerdir.
public static async Task YieldLoopExample()
{
await Task.Run(async delegate
{
for (int i = 0; i < 1000000; i++)
{
await Task.Yield(); // fork the continuation into a separate work item
}
});
}
Public Async Function YieldLoopExample() As Task
Await Task.Run(Async Function()
For i As Integer = 0 To 999999
Await Task.Yield() ' fork the continuation into a separate work item
Next
End Function)
End Function
Asenkron bir yöntemde askıya alma ve yeniden başlatma üzerinde daha iyi denetim sağlamak için Task.ConfigureAwait yöntemini de kullanabilirsiniz. Daha önce belirtildiği gibi, geçerli bağlam varsayılan olarak zaman uyumsuz bir yöntem askıya alınırken yakalanır ve bu yakalanan bağlam, zaman uyumsuz yöntemin sürdürmesini yeniden başlatma sırasında çağırmak için kullanılır. Çoğu durumda, tam olarak istediğiniz davranış budur. Diğer durumlarda, devamlılık bağlamını umursamıyor olabilirsiniz ve bu tür gönderilere tekrar özgün bağlama başvurmaktan kaçınarak daha iyi bir performans elde edebilirsiniz. Bu davranışı etkinleştirmek için Task.ConfigureAwait yöntemini kullanarak, await işlemini bağlamı yakalayıp sürdürmemesi, fakat beklenilen zaman uyumsuz işlemin tamamlandığı her yerde yürütmeye devam etmesi yönünde bilgilendirin.
await someTask.ConfigureAwait(continueOnCapturedContext:false);
Awaitables, ConfigureAwait ve SynchronizationContext
await, yalnızca Task ile değil, beklenebilir ifade desenini karşılayan herhangi bir türle çalışır. Bir tür, IsCompleted, OnCompleted ve GetResult üyelerine sahip bir tür döndüren uyumlu bir GetAwaiter yöntemi sağlıyorsa beklenebilir. Çoğu halka açık API'de Task, Task<TResult>, ValueTask veya ValueTask<TResult> döndürür. Özel beklenebilirleri yalnızca özel senaryolar için kullanın.
Çağıranın bağlamına ihtiyaç duyulmadığında ConfigureAwait kullanın. Uygulama kodunda, kullanıcı arabirimini güncellemek için genellikle bağlamın yakalanması gerekir. Yeniden kullanılabilir kitaplık kodunda, ConfigureAwait(false) genellikle gereksiz bağlam atlamalarını önlediğinden ve engelleyen arayanlar için kilitlenme riskini azalttığı için tercih edilir.
ConfigureAwait(false) devamlılık zamanlamasını değiştirir, ExecutionContext akışını değil. Bağlam davranışının daha ayrıntılı açıklaması için bkz. ExecutionContext ve SynchronizationContext.
Zaman uyumsuz işlemi iptal etme
.NET Framework 4'den başlayarak, iptali destekleyen TAP yöntemleri, iptal belirteci (CancellationToken nesnesi) kabul eden en az bir aşırı yükleme sağlar.
İptal belirteci kaynağı (CancellationTokenSource nesnesi) aracılığıyla bir iptal belirteci oluşturursunuz. Kaynağın Token özelliği, kaynağın Cancel yöntemi çağrıldığında iptal belirtecine sinyal verir.
var cts = new CancellationTokenSource();
string result = await DownloadStringTaskAsync(url, cts.Token);
… // at some point later, potentially on another thread
cts.Cancel();
Örneğin, tek bir web sayfasını indirmek ve işlemi iptal edebilmek istiyorsanız, bir CancellationTokenSource nesne oluşturun, belirtecini TAP yöntemine geçirin ve işlemi iptal etmeye hazır olduğunuzda kaynağın Cancel yöntemini çağırın:
var cts = new CancellationTokenSource();
IList<string> results = await Task.WhenAll(from url in urls select DownloadStringTaskAsync(url, cts.Token));
// at some point later, potentially on another thread
…
cts.Cancel();
Ya da aynı belirteci işlemlerin seçmeli bir alt kümesine geçirebilirsiniz:
var cts = new CancellationTokenSource();
byte [] data = await DownloadDataAsync(url, cts.Token);
await SaveToDiskAsync(outputPath, data, CancellationToken.None);
… // at some point later, potentially on another thread
cts.Cancel();
Önemli
Herhangi bir iş parçacığı iptal istekleri başlatabilir.
İptalin hiçbir zaman talep edilmeyeceğini belirtmek için iptal belirtecini kabul eden herhangi bir yönteme CancellationToken.None değerini sağlayabilirsiniz. Bu değer, özelliğin CancellationToken.CanBeCanceled olarak false değer döndürmesine neden olur ve çağrılan yöntem uygun şekilde optimize edebilir. Test amacıyla, belirtecin zaten iptal edilmiş veya iptal edilemeyen bir durumda başlaması gerekip gerekmediğini belirtmek için Boole değeri kabul eden oluşturucu kullanılarak örneği oluşturulmuş önceden iptal edilmiş bir iptal belirteci de geçirebilirsiniz.
bu iptal yaklaşımının çeşitli avantajları vardır:
Aynı iptal belirtecini istediğiniz sayıda zaman uyumsuz ve zaman uyumlu işlemlere geçirebilirsiniz.
Aynı iptal isteği istediğiniz sayıda dinleyiciye gidebilir.
Zaman uyumsuz API'nin geliştiricisi, iptalin istenip istenemeyeceği ve ne zaman geçerli olacağı üzerinde tam denetime sahiptir.
API'yi kullanan kod, iptal isteklerinin gönderildiği asenkron çağrıları seçmeli olarak belirleyebilir.
İlerlemenin İzlenmesi
Bazı asenkron yöntemler, bir ilerleme arabirimi aracılığıyla ilerlemeyi ortaya koyar ki bu arabirimi asenkron yönteme geçirirsiniz. Örneğin, zaman uyumsuz olarak bir metin dizesi indiren ve yol boyunca şimdiye kadar tamamlanan indirme yüzdesini içeren ilerleme güncelleştirmelerini yükselten bir işlev düşünün. Böyle bir yöntemi bir Windows Presentation Foundation (WPF) uygulamasında aşağıdaki gibi kullanabilirsiniz:
private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
btnDownload.IsEnabled = false;
try
{
txtResult.Text = await DownloadStringTaskAsync(txtUrl.Text,
new Progress<int>(p => pbDownloadProgress.Value = p));
}
finally { btnDownload.IsEnabled = true; }
}
Yerleşik görev tabanlı birleştiricileri kullanma
System.Threading.Tasks ad alanı, görevleri oluşturmak ve görevlerle çalışmak için çeşitli yöntemler içerir.
Uyarı
Bu bölümdeki çeşitli kod örnekleri, Bitmap paketini gerektiren ve yalnızca Windows'da desteklenen System.Drawing.Common kullanır. Gösterdikleri eşzamansız desenler tüm platformlarda geçerlidir; Windows dışı hedefler için çok platformlu bir görüntüleme kitaplığı ile değiştirin.
Task.Run
Task sınıfı, işleri bir Task veya Task<TResult> olarak iş parçacığı havuzuna kolayca devretmenizi sağlayan çeşitli Run yöntemler içerir. Örneğin:
public static async Task TaskRunBasicExample()
{
int answer = 42;
string result = await Task.Run(() =>
{
// … do compute-bound work here
return answer.ToString();
});
Console.WriteLine(result);
}
Public Async Function TaskRunBasicExample() As Task
Dim answer As Integer = 42
Dim result As String = Await Task.Run(Function()
' … do compute-bound work here
Return answer.ToString()
End Function)
Console.WriteLine(result)
End Function
Örneğin, aşırı yükleme yöntemi gibi bazı Run yöntemler, Task.Run(Func<Task>) yönteminin kısaltması olarak mevcuttur. Bu aşırı yükleme, await'ün boşaltılan iş içinde kullanılmasına olanak tanır. Örneğin:
public static async Task TaskRunAsyncExample()
{
Bitmap image = await Task.Run(async () =>
{
using Bitmap bmp1 = await Stubs.DownloadFirstImageAsync();
using Bitmap bmp2 = await Stubs.DownloadSecondImageAsync();
return Stubs.Mashup(bmp1, bmp2);
});
}
Public Async Function TaskRunAsyncExample() As Task
Dim image As Bitmap = Await Task.Run(Async Function()
Using bmp1 As Bitmap = Await Stubs.DownloadFirstImageAsync()
Using bmp2 As Bitmap = Await Stubs.DownloadSecondImageAsync()
Return Stubs.Mashup(bmp1, bmp2)
End Using
End Using
End Function)
End Function
Bu tür aşırı yüklemeler mantıksal olarak, Task Parallel Library içerisindeki TaskFactory.StartNew yöntemi ile uzantı yöntemi Unwrap'in eş zamanlı kullanımına eşdeğerdir.
Task.FromResult
Verilerin zaten mevcut olabileceği ve görev döndüren bir yöntemi bir Task<TResult> içerisine kaldırmanız gerektiği senaryolarda FromResult yöntemini kullanın.
public static Task<int> GetValueAsync(string key)
{
int cachedValue;
return Stubs.TryGetCachedValue(out cachedValue) ?
Task.FromResult(cachedValue) :
GetValueAsyncInternal(key);
}
static async Task<int> GetValueAsyncInternal(string key)
{
await Task.Delay(1);
return 0;
}
Public Function GetValueAsync(key As String) As Task(Of Integer)
Dim cachedValue As Integer
If Stubs.TryGetCachedValue(cachedValue) Then
Return Task.FromResult(cachedValue)
Else
Return GetValueAsyncInternal(key)
End If
End Function
Private Async Function GetValueAsyncInternal(key As String) As Task(Of Integer)
Await Task.Delay(1)
Return 0
End Function
Task.WhenAll
Birden çok zaman uyumsuz işlemi görev olarak temsil edenlerde zaman uyumsuz olarak beklemek için WhenAll yöntemini kullanın. yöntemi, genel olmayan bir görev kümesini veya tekdüzen olmayan bir genel görev kümesini destekleyen birden çok aşırı yüklemeye sahiptir (örneğin, birden çok geçersiz dönüş işlemi için zaman uyumsuz olarak bekleme veya her değerin farklı türde olabileceği birden çok değer döndüren yöntemi zaman uyumsuz olarak bekleme) ve tekdüzen bir genel görev kümesini (örneğin, birden çok TResultdönen yöntemi bekleme) destekler.
Birkaç müşteriye e-posta iletileri göndermek istediğinizi varsayalım. İletileri gönderirken zamanlamalarını çakıştırabilirsiniz, böylece bir iletinin tamamlanmasını beklemeden bir sonraki iletiyi gönderebilirsiniz. Ayrıca gönderme işlemlerinin ne zaman tamamlanıp tamamlanmadığını ve herhangi bir hata oluşup oluşmadığını da öğrenebilirsiniz:
IEnumerable<Task> asyncOps = from addr in addrs select SendMailAsync(addr);
await Task.WhenAll(asyncOps);
Bu kod, oluşabilecek özel durumları açıkça işlemez, ancak özel durumların await'dan çıkan WhenAll sonucundaki göreve yayılmasına izin verir. Özel durumları işlemek için aşağıdaki gibi bir kod kullanın:
public static async Task WhenAllWithCatch()
{
IEnumerable<Task> asyncOps = from addr in Stubs.addrs select Stubs.SendMailAsync(addr);
try
{
await Task.WhenAll(asyncOps);
}
catch (Exception exc)
{
Console.WriteLine(exc);
}
}
Public Async Function WhenAllWithCatch() As Task
Dim asyncOps As IEnumerable(Of Task) = From addr In Stubs.addrs Select Stubs.SendMailAsync(addr)
Try
Await Task.WhenAll(asyncOps)
Catch exc As Exception
Console.WriteLine(exc)
End Try
End Function
Bu durumda, herhangi bir zaman uyumsuz işlem başarısız olursa, tüm özel durumlar, WhenAll yöntemi tarafından döndürülen Task içinde depolanan AggregateException özel durumda birleştirilir. Ancak bu istisnalardan yalnızca biri await anahtar sözcüğüyle aktarılır. Tüm özel durumları incelemek istiyorsanız, önceki kodu aşağıdaki gibi yeniden yazabilirsiniz:
public static async Task WhenAllExamineExceptions()
{
Task[] asyncOps = (from addr in Stubs.addrs select Stubs.SendMailAsync(addr)).ToArray();
try
{
await Task.WhenAll(asyncOps);
}
catch (Exception exc)
{
foreach (Task faulted in asyncOps.Where(t => t.IsFaulted))
{
Console.WriteLine($"Faulted: {faulted.Exception}");
}
}
}
Public Async Function WhenAllExamineExceptions() As Task
Dim asyncOps As Task() = (From addr In Stubs.addrs Select Stubs.SendMailAsync(addr)).ToArray()
Try
Await Task.WhenAll(asyncOps)
Catch exc As Exception
For Each faulted As Task In asyncOps.Where(Function(t) t.IsFaulted)
Console.WriteLine($"Faulted: {faulted.Exception}")
Next
End Try
End Function
Web'den zaman uyumsuz olarak birden çok dosya indirme örneği düşünün. Bu durumda, tüm zaman uyumsuz işlemler homojen sonuç türlerine sahiptir ve sonuçlara kolayca erişebilirsiniz:
string [] pages = await Task.WhenAll(
from url in urls select DownloadStringTaskAsync(url));
Önceki void-returning senaryosunda açıklanan özel durum işleme tekniklerinin aynısını kullanabilirsiniz:
public static async Task WhenAllDownloadPagesExceptions()
{
Task<string>[] asyncOps =
(from url in Stubs.urls select Stubs.DownloadStringTaskAsync(url)).ToArray();
try
{
string[] pages = await Task.WhenAll(asyncOps);
Console.WriteLine(pages.Length);
}
catch (Exception exc)
{
foreach (Task<string> faulted in asyncOps.Where(t => t.IsFaulted))
{
Console.WriteLine($"Faulted: {faulted.Exception}");
}
}
}
Public Async Function WhenAllDownloadPagesExceptions() As Task
Dim asyncOps As Task(Of String)() =
(From url In Stubs.urls Select Stubs.DownloadStringTaskAsync(url)).ToArray()
Try
Dim pages As String() = Await Task.WhenAll(asyncOps)
Console.WriteLine(pages.Length)
Catch exc As Exception
For Each faulted As Task(Of String) In asyncOps.Where(Function(t) t.IsFaulted)
Console.WriteLine($"Faulted: {faulted.Exception}")
Next
End Try
End Function
Task.WhenAny
Birden çok zaman uyumsuz işlemin görevler olarak temsil edildiği durumda, yalnızca birinin tamamlanmasını zaman uyumsuz olarak beklemek için WhenAny yöntemini kullanın. Bu yöntem dört birincil kullanım örneği sunar:
Yedeklilik: Bir işlemi birden çok kez gerçekleştirme ve ilk tamamlananı seçme (örneğin, tek bir sonuç döndüren birden çok hisse senedi teklifi web hizmetiyle iletişim kurma ve en hızlı işlemi tamamlayanı seçme).
Araya ekleme: Birden çok işlem başlatma ve tümünün tamamlanmasını bekleme, ancak tamamlandıkça işleme.
Kısıtlama: Diğer işlemler tamamlandıkça ek işlemlerin başlamasına izin verme. Bu senaryo, araya ekleme senaryosunun bir uzantısıdır.
Erken kurtarma: Örneğin, t1 göreviyle temsil edilen bir işlem, t2 göreviyle birlikte bir WhenAny görevde gruplandırılabilir ve WhenAny görevini bekleyebilirsiniz. T2 görevi zaman aşımını, iptali veya t1 tamamlanmadan önce WhenAny görevinin tamamlanmasına neden olan başka bir sinyali temsil edebilir.
Yedeklilik
Hisse senedi satın alıp almayacağınıza karar vermek istediğiniz bir olayı düşünün. Güvendiğiniz çeşitli stok önerisi web hizmetleri vardır, ancak günlük yüke bağlı olarak her hizmet farklı zamanlarda yavaş olabilir. WhenAny Herhangi bir işlem tamamlandığında bildirim almak için yöntemini kullanın:
public static async Task WhenAnyRedundancy(string symbol)
{
var recommendations = new List<Task<bool>>()
{
Stubs.GetBuyRecommendation1Async(symbol),
Stubs.GetBuyRecommendation2Async(symbol),
Stubs.GetBuyRecommendation3Async(symbol)
};
Task<bool> recommendation = await Task.WhenAny(recommendations);
if (await recommendation) Stubs.BuyStock(symbol);
}
Public Async Function WhenAnyRedundancy(symbol As String) As Task
Dim recommendations As New List(Of Task(Of Boolean)) From {
Stubs.GetBuyRecommendation1Async(symbol),
Stubs.GetBuyRecommendation2Async(symbol),
Stubs.GetBuyRecommendation3Async(symbol)
}
Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(recommendations)
If Await recommendation Then Stubs.BuyStock(symbol)
End Function
'nin aksine WhenAll, başarıyla tamamlanan tüm görevlerin eşlenmemiş sonuçlarını döndürür, WhenAny tamamlanan görevi döndürür. Bir görev başarısız olursa, başarısız olduğunu bilmek önemlidir ve bir görev başarılı olursa, dönüş değerinin hangi görevle ilişkilendirildiğini bilmek önemlidir. Bu nedenle, döndürülen görevin sonucuna erişmeniz veya bu örnekte gösterildiği gibi daha fazla beklemeniz gerekir.
' WhenAllde olduğu gibi özel durumlara da uyum sağlayabilmek gerekir. Tamamlanmış görevi geri aldığınızda, döndürülen görevdeki hataların yayılmasını bekleyebilir ve bunları uygun şekilde düzeltebilirsiniz; örneğin:
public static async Task WhenAnyRetryOnException(string symbol)
{
Task<bool>[] allRecommendations = new Task<bool>[]
{
Stubs.GetBuyRecommendation1Async(symbol),
Stubs.GetBuyRecommendation2Async(symbol),
Stubs.GetBuyRecommendation3Async(symbol)
};
var remaining = allRecommendations.ToList();
while (remaining.Count > 0)
{
Task<bool> recommendation = await Task.WhenAny(remaining);
try
{
if (await recommendation) Stubs.BuyStock(symbol);
break;
}
catch (WebException)
{
remaining.Remove(recommendation);
}
}
}
Public Async Function WhenAnyRetryOnException(symbol As String) As Task
Dim allRecommendations As Task(Of Boolean)() = {
Stubs.GetBuyRecommendation1Async(symbol),
Stubs.GetBuyRecommendation2Async(symbol),
Stubs.GetBuyRecommendation3Async(symbol)
}
Dim remaining As List(Of Task(Of Boolean)) = allRecommendations.ToList()
While remaining.Count > 0
Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(remaining)
Try
If Await recommendation Then Stubs.BuyStock(symbol)
Exit While
Catch ex As WebException
remaining.Remove(recommendation)
End Try
End While
End Function
Ayrıca, ilk görev başarıyla tamamlanırsa bile sonraki görevler başarısız olabilir. Bu noktada, özel durumlarla ilgilenmek için çeşitli seçenekleriniz vardır: Başlatılan tüm görevlerin tamamlanmasını bekleyebilir, bu durumda yöntemini kullanabilir WhenAll veya tüm özel durumların önemli olduğuna ve günlüğe kaydedilmesi gerektiğine karar vekleyebilirsiniz. Bu senaryoda, görevler zaman uyumsuz olarak tamamlandığında bildirim almak için devamlılıkları kullanabilirsiniz:
foreach(Task recommendation in recommendations)
{
var ignored = recommendation.ContinueWith(
t => { if (t.IsFaulted) Log(t.Exception); });
}
veya:
foreach(Task recommendation in recommendations)
{
var ignored = recommendation.ContinueWith(
t => Log(t.Exception), TaskContinuationOptions.OnlyOnFaulted);
}
veya hatta:
private static async void LogCompletionIfFailed(IEnumerable<Task> tasks)
{
foreach (var task in tasks)
{
try { await task; }
catch (Exception exc) { Stubs.Log(exc); }
}
}
Private Async Sub LogCompletionIfFailed(tasks As IEnumerable(Of Task))
For Each task In tasks
Try
Await task
Catch exc As Exception
Stubs.Log(exc)
End Try
Next
End Sub
Son olarak, kalan tüm işlemleri iptal etmek isteyebilirsiniz:
public static async Task WhenAnyCancelRemainder(string symbol)
{
var cts = new CancellationTokenSource();
var recommendations = new List<Task<bool>>()
{
Stubs.GetBuyRecommendation1Async(symbol, cts.Token),
Stubs.GetBuyRecommendation2Async(symbol, cts.Token),
Stubs.GetBuyRecommendation3Async(symbol, cts.Token)
};
Task<bool> recommendation = await Task.WhenAny(recommendations);
cts.Cancel();
if (await recommendation) Stubs.BuyStock(symbol);
}
Public Async Function WhenAnyCancelRemainder(symbol As String) As Task
Dim cts As New CancellationTokenSource()
Dim recommendations As New List(Of Task(Of Boolean)) From {
Stubs.GetBuyRecommendation1Async(symbol, cts.Token),
Stubs.GetBuyRecommendation2Async(symbol, cts.Token),
Stubs.GetBuyRecommendation3Async(symbol, cts.Token)
}
Dim recommendation As Task(Of Boolean) = Await Task.WhenAny(recommendations)
cts.Cancel()
If Await recommendation Then Stubs.BuyStock(symbol)
End Function
Dolaşım
Web'den görüntü indirdiğiniz ve her görüntüyü işleyeceğiniz bir durum düşünün (örneğin, görüntüyü kullanıcı arabirimi denetimine ekleme). Ui iş parçacığında görüntüleri sıralı olarak işlersiniz, ancak görüntüleri mümkün olduğunca eşzamanlı olarak indirmek istersiniz. Ayrıca, görüntülerin tümü indirilene kadar kullanıcı arabirimine eklemeye devam etmek istemezsiniz. Bunun yerine, tamamlandıklarında eklemeyi tercih edersiniz.
public static async Task WhenAnyInterleaving(string[] imageUrls)
{
List<Task<Bitmap>> imageTasks =
(from imageUrl in imageUrls select Stubs.GetBitmapAsync(imageUrl)).ToList();
while (imageTasks.Count > 0)
{
try
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);
Bitmap image = await imageTask;
Console.WriteLine($"Got image: {image.Width}x{image.Height}");
}
catch { }
}
}
Public Async Function WhenAnyInterleaving(imageUrls As String()) As Task
Dim imageTasks As List(Of Task(Of Bitmap)) =
(From imageUrl In imageUrls Select Stubs.GetBitmapAsync(imageUrl)).ToList()
While imageTasks.Count > 0
Try
Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks)
imageTasks.Remove(imageTask)
Dim image As Bitmap = Await imageTask
Console.WriteLine($"Got image: {image.Width}x{image.Height}")
Catch
End Try
End While
End Function
ayrıca, indirilen görüntülerin üzerinde yoğun işlem gerektiren bir senaryoya ThreadPool da araya ekleme uygulayabilirsiniz; örneğin:
public static async Task WhenAnyInterleavingWithProcessing(string[] imageUrls)
{
List<Task<Bitmap>> imageTasks =
(from imageUrl in imageUrls
select Stubs.GetBitmapAsync(imageUrl)
.ContinueWith(t => Stubs.ConvertImage(t.Result))).ToList();
while (imageTasks.Count > 0)
{
try
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);
Bitmap image = await imageTask;
Console.WriteLine($"Got image: {image.Width}x{image.Height}");
}
catch { }
}
}
Public Async Function WhenAnyInterleavingWithProcessing(imageUrls As String()) As Task
Dim imageTasks As List(Of Task(Of Bitmap)) =
(From imageUrl In imageUrls
Select Stubs.GetBitmapAsync(imageUrl).ContinueWith(Function(t) Stubs.ConvertImage(t.Result))).ToList()
While imageTasks.Count > 0
Try
Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks)
imageTasks.Remove(imageTask)
Dim image As Bitmap = Await imageTask
Console.WriteLine($"Got image: {image.Width}x{image.Height}")
Catch
End Try
End While
End Function
Hız kısıtlama
İndirmelerin sınırlandırılması gerektiği kadar çok sayıda görüntü indiren kullanıcı örneği hariç, araya serpiştirme örneğini düşünün. Örneğin, yalnızca belirli sayıda indirmenin eşzamanlı olarak gerçekleşmesini istiyorsunuz. Bu hedefe ulaşmak için zaman uyumsuz işlemlerin bir alt kümesini başlatın. İşlemler tamamlandıktan sonra, yerlerine geçmek için ek işlemler başlatabilirsiniz:
public static async Task WhenAnyThrottling(Uri[] uriList)
{
const int CONCURRENCY_LEVEL = 15;
int nextIndex = 0;
var imageTasks = new List<Task<Bitmap>>();
while (nextIndex < CONCURRENCY_LEVEL && nextIndex < uriList.Length)
{
imageTasks.Add(Stubs.GetBitmapAsync(uriList[nextIndex].ToString()));
nextIndex++;
}
while (imageTasks.Count > 0)
{
try
{
Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
imageTasks.Remove(imageTask);
Bitmap image = await imageTask;
Console.WriteLine($"Got image: {image.Width}x{image.Height}");
}
catch (Exception exc) { Stubs.Log(exc); }
if (nextIndex < uriList.Length)
{
imageTasks.Add(Stubs.GetBitmapAsync(uriList[nextIndex].ToString()));
nextIndex++;
}
}
}
Public Async Function WhenAnyThrottling(uriList As Uri()) As Task
Const CONCURRENCY_LEVEL As Integer = 15
Dim nextIndex As Integer = 0
Dim imageTasks As New List(Of Task(Of Bitmap))
While nextIndex < CONCURRENCY_LEVEL AndAlso nextIndex < uriList.Length
imageTasks.Add(Stubs.GetBitmapAsync(uriList(nextIndex).ToString()))
nextIndex += 1
End While
While imageTasks.Count > 0
Try
Dim imageTask As Task(Of Bitmap) = Await Task.WhenAny(imageTasks)
imageTasks.Remove(imageTask)
Dim image As Bitmap = Await imageTask
Console.WriteLine($"Got image: {image.Width}x{image.Height}")
Catch exc As Exception
Stubs.Log(exc)
End Try
If nextIndex < uriList.Length Then
imageTasks.Add(Stubs.GetBitmapAsync(uriList(nextIndex).ToString()))
nextIndex += 1
End If
End While
End Function
Erken Kurtarma
Kullanıcının iptal isteğine aynı anda yanıt verirken bir işlemin tamamlanmasını zaman uyumsuz olarak beklediğinizi düşünün (örneğin, kullanıcı bir iptal düğmesine tıkladı). Aşağıdaki kodda bu senaryo gösterilmektedir:
class EarlyBailoutUI
{
private CancellationTokenSource? m_cts;
public void btnCancel_Click(object sender, EventArgs e)
{
if (m_cts != null) m_cts.Cancel();
}
public async void btnRun_Click(object sender, EventArgs e)
{
m_cts = new CancellationTokenSource();
try
{
Task<Bitmap> imageDownload = Stubs.GetBitmapAsync("url");
await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token);
if (imageDownload.IsCompleted)
{
Bitmap image = await imageDownload;
Stubs.Log(image);
}
else imageDownload.ContinueWith(t => Stubs.Log(t));
}
finally { }
}
}
Class EarlyBailoutUI
Private m_cts As CancellationTokenSource
Public Sub btnCancel_Click(sender As Object, e As EventArgs)
If m_cts IsNot Nothing Then m_cts.Cancel()
End Sub
Public Async Sub btnRun_Click(sender As Object, e As EventArgs)
m_cts = New CancellationTokenSource()
Try
Dim imageDownload As Task(Of Bitmap) = Stubs.GetBitmapAsync("url")
Await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token)
If imageDownload.IsCompleted Then
Dim image As Bitmap = Await imageDownload
Stubs.Log(image)
Else
imageDownload.ContinueWith(Sub(t) Stubs.Log(t))
End If
Finally
End Try
End Sub
End Class
Bu uygulama, siz vazgeçmeye karar verir vermez kullanıcı arabirimini yeniden etkinleştirir, ancak arka plandaki zaman uyumsuz işlemleri iptal etmez. Bir diğer alternatif, süreçten vazgeçmeye karar verdiğinizde bekleyen işlemleri iptal etmek, ancak iptal isteği nedeniyle erken sona erme ihtimali olan işlemler tamamlanana kadar kullanıcı arabirimini yeniden oluşturmamaktır.
class EarlyBailoutWithTokenUI
{
private CancellationTokenSource? m_cts;
public async void btnRun_Click(object sender, EventArgs e)
{
m_cts = new CancellationTokenSource();
try
{
Task<Bitmap> imageDownload = Stubs.GetBitmapAsync("url", m_cts.Token);
await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token);
Bitmap image = await imageDownload;
Stubs.Log(image);
}
catch (OperationCanceledException) { }
finally { }
}
}
Class EarlyBailoutWithTokenUI
Private m_cts As CancellationTokenSource
Public Async Sub btnRun_Click(sender As Object, e As EventArgs)
m_cts = New CancellationTokenSource()
Try
Dim imageDownload As Task(Of Bitmap) = Stubs.GetBitmapAsync("url", m_cts.Token)
Await Examples.UntilCompletionOrCancellation(imageDownload, m_cts.Token)
Dim image As Bitmap = Await imageDownload
Stubs.Log(image)
Catch ex As OperationCanceledException
Finally
End Try
End Sub
End Class
Erken kurtarma yöntemlerinin bir başka örneği, sonraki bölümde tartışıldığı gibi WhenAny yöntemi ile Delay yönteminin birlikte kullanılmasıdır.
Task.Delay
Asenkron bir yöntemin yürütülmesine duraklamalar eklemek için Task.Delay yöntemini kullanın. Bu duraklama, yoklama döngüleri oluşturma ve önceden belirlenmiş bir süre boyunca kullanıcı girişinin işlenmesini geciktirme gibi birçok işlev türü için kullanışlıdır. ile Task.Delay yöntemini Task.WhenAny ile birlikte bekleme sırasında zaman aşımlarını uygulamak için kullanabilirsiniz.
Daha büyük bir asenkron işlemin parçası olan bir görev (örneğin, bir ASP.NET web hizmeti) tamamlanması çok uzun sürerse ve hiç tamamlanamazsa, genel işlem zarar görebilir. Bu nedenle, zaman uyumsuz bir işlem beklerken zaman aşımı belirleyebilmek önemlidir. Senkron Task.Wait, Task.WaitAll ve Task.WaitAny yöntemleri zaman aşımı değerlerini kabul eder, ancak ilgili TaskFactory.ContinueWhenAll/TaskFactory.ContinueWhenAny ve daha önce bahsedilen Task.WhenAll/Task.WhenAny yöntemler kabul etmez. Bunun yerine, zaman aşımına Task.Delay ve Task.WhenAny birlikte kullanarak uygulayın.
Örneğin, kullanıcı arabirimi uygulamanızda bir görüntü indirmek istediğinizi ve görüntü indirilirken kullanıcı arabirimini devre dışı bırakmak istediğinizi varsayalım. Ancak indirme işlemi çok uzun sürerse kullanıcı arabirimini yeniden etkinleştirmek ve indirmeyi atmak istersiniz:
public static async Task<Bitmap?> DownloadWithTimeout(string url)
{
Task<Bitmap> download = Stubs.GetBitmapAsync(url);
if (download == await Task.WhenAny(download, Task.Delay(3000)))
{
return await download;
}
else
{
var ignored = download.ContinueWith(
t => Trace($"Task finally completed: {t.Status}"));
return null;
}
}
static void Trace(string message) => Console.WriteLine(message);
Public Async Function DownloadWithTimeout(url As String) As Task(Of Bitmap)
Dim download As Task(Of Bitmap) = Stubs.GetBitmapAsync(url)
If download Is Await Task.WhenAny(download, Task.Delay(3000)) Then
Return Await download
Else
Dim ignored = download.ContinueWith(Sub(t) TraceMsg($"Task finally completed: {t.Status}"))
Return Nothing
End If
End Function
Aynı ilke birden çok indirme için geçerlidir çünkü WhenAll bir görev döndürür:
public static async Task<Bitmap[]?> DownloadMultipleWithTimeout(string[] imageUrls)
{
Task<Bitmap[]> downloads =
Task.WhenAll(from url in imageUrls select Stubs.GetBitmapAsync(url));
if (downloads == await Task.WhenAny(downloads, Task.Delay(3000)))
{
return await downloads;
}
else
{
downloads.ContinueWith(t => Stubs.Log(t));
return null;
}
}
Public Async Function DownloadMultipleWithTimeout(imageUrls As String()) As Task(Of Bitmap())
Dim downloads As Task(Of Bitmap()) =
Task.WhenAll(From url In imageUrls Select Stubs.GetBitmapAsync(url))
If downloads Is Await Task.WhenAny(downloads, Task.Delay(3000)) Then
Return Await downloads
Else
downloads.ContinueWith(Sub(t) Stubs.Log(t))
Return Nothing
End If
End Function
Görev Tabanlı Birleştiriciler Oluşturma
Bir görev, zamansız bir işlemi tamamen temsil edebildiğinden ve işlemle katılmak, sonuçlarını almak vb. için senkron ve asenkron özellikler sağlayabildiğinden, daha büyük desenler oluşturmak için görevleri birleştiren kullanışlı birleştirici kitaplıklar oluşturabilirsiniz. Önceki bölümde açıklandığı gibi.NET birkaç yerleşik birleştirici içerir, ancak kendiniz de oluşturabilirsiniz. Aşağıdaki bölümlerde olası birleştirici yöntemlerinin ve türlerinin birkaç örneği verilmiştir.
RetryOnFault
Birçok durumda, önceki bir deneme başarısız olursa işlemi yeniden denemek istersiniz. Zaman uyumlu kod için, bu görevi gerçekleştirmek için aşağıdaki örnekte olduğu gibi RetryOnFault bir yardımcı yöntem oluşturabilirsiniz:
public static T RetryOnFault<T>(Func<T> function, int maxTries)
{
for (int i = 0; i < maxTries; i++)
{
try { return function(); }
catch { if (i == maxTries - 1) throw; }
}
return default(T)!;
}
Public Function RetryOnFaultSync(Of T)(func As Func(Of T), maxTries As Integer) As T
For i As Integer = 0 To maxTries - 1
Try
Return func()
Catch
If i = maxTries - 1 Then Throw
End Try
Next
Return Nothing
End Function
TAP ile gerçekleştirilen ve dolayısıyla görev nesnelerini döndüren asenkron işlemler için neredeyse birebir yardımcı metodu oluşturabilirsiniz:
public static async Task<T> RetryOnFault<T>(Func<Task<T>> function, int maxTries)
{
for (int i = 0; i < maxTries; i++)
{
try { return await function().ConfigureAwait(false); }
catch { if (i == maxTries - 1) throw; }
}
return default(T)!;
}
Public Async Function RetryOnFault(Of T)(func As Func(Of Task(Of T)), maxTries As Integer) As Task(Of T)
For i As Integer = 0 To maxTries - 1
Try
Return Await func().ConfigureAwait(False)
Catch
If i = maxTries - 1 Then Throw
End Try
Next
Return Nothing
End Function
Daha sonra uygulamanın mantığına yeniden denemeleri kodlamak için bu birleştiriciyi kullanabilirsiniz. Örneğin:
// Download the URL, trying up to three times in case of failure
string pageContents = await RetryOnFault(
() => DownloadStringTaskAsync(url), 3);
İşlevi RetryOnFault daha da genişletebilirsiniz. Örneğin işlev, işlemin yeniden ne zaman deneneceğini belirlemek için işlevin yeniden denemeler arasında çağırdığını başka bir Func<Task> işlemi kabul edebilir. Örneğin:
public static async Task<T> RetryOnFaultWithDelay<T>(
Func<Task<T>> function, int maxTries, Func<Task> retryWhen)
{
for (int i = 0; i < maxTries; i++)
{
try { return await function().ConfigureAwait(false); }
catch { if (i == maxTries - 1) throw; }
await retryWhen().ConfigureAwait(false);
}
return default(T)!;
}
Public Async Function RetryOnFaultWithDelay(Of T)(
func As Func(Of Task(Of T)), maxTries As Integer, retryWhen As Func(Of Task)) As Task(Of T)
For i As Integer = 0 To maxTries - 1
Try
Return Await func().ConfigureAwait(False)
Catch
If i = maxTries - 1 Then Throw
End Try
Await retryWhen().ConfigureAwait(False)
Next
Return Nothing
End Function
Ardından, işlemi yeniden denemeden önce bir saniye beklemek için işlevini aşağıdaki gibi kullanabilirsiniz:
// Download the URL, trying up to three times in case of failure,
// and delaying for a second between retries
string pageContents = await RetryOnFault(
() => DownloadStringTaskAsync(url), 3, () => Task.Delay(1000));
NeedOnlyOne
Bazen, bir işlemin gecikme süresini ve başarı şansını geliştirmek için yedeklilikten yararlanabilirsiniz. Hisse senedi fiyatları sağlayan birden çok web hizmetini göz önünde bulundurun, ancak günün çeşitli saatlerinde her hizmet farklı kalite ve yanıt süreleri düzeyleri sağlayabilir. Bu dalgalanmalarla başa çıkmak için tüm web hizmetlerine istek gönderebilir ve bir yanıt alır almaz kalan istekleri iptal edebilirsiniz. Birden çok işlem başlatma, bekleme ve geri kalan işlemleri iptal etme gibi yaygın bir düzeni uygulamayı kolaylaştırmak için bir yardımcı işlevi uygulayabilirsiniz.
NeedOnlyOne Aşağıdaki örnekteki işlev bu senaryoyu gösterir:
public static async Task<T> NeedOnlyOne<T>(
params Func<CancellationToken, Task<T>>[] functions)
{
var cts = new CancellationTokenSource();
var tasks = (from function in functions
select function(cts.Token)).ToArray();
var completed = await Task.WhenAny(tasks).ConfigureAwait(false);
cts.Cancel();
foreach (var task in tasks)
{
var ignored = task.ContinueWith(
t => Stubs.Log(t), TaskContinuationOptions.OnlyOnFaulted);
}
return await completed;
}
Public Async Function NeedOnlyOne(Of T)(
ParamArray functions As Func(Of CancellationToken, Task(Of T))()) As Task(Of T)
Dim cts As New CancellationTokenSource()
Dim tasks As Task(Of T)() = (From func In functions Select func(cts.Token)).ToArray()
Dim completed As Task(Of T) = Await Task.WhenAny(tasks).ConfigureAwait(False)
cts.Cancel()
For Each task In tasks
Dim ignored = task.ContinueWith(
Sub(tsk) Stubs.Log(tsk), TaskContinuationOptions.OnlyOnFaulted)
Next
Return Await completed
End Function
Ardından bu işlevi aşağıdaki gibi kullanabilirsiniz:
double currentPrice = await NeedOnlyOne(
ct => GetCurrentPriceFromServer1Async("msft", ct),
ct => GetCurrentPriceFromServer2Async("msft", ct),
ct => GetCurrentPriceFromServer3Async("msft", ct));
İç içe geçmiş işlemler
Çakışan senaryoları desteklemek için WhenAny yönteminin kullanılması, büyük görev kümeleriyle çalışırken performans sorunlarına neden olabilir. Her çağrı, WhenAny her göreve bir devamlılık kaydeder. N görev sayısı için bu işlem, araya ekleme işleminin ömrü boyunca O(N2) devamlılıkları oluşturur. Büyük bir görev kümesiyle çalışıyorsanız, performans sorununu çözmek için bir birleştirici (Interleaved aşağıdaki örnekte) kullanın:
public static IEnumerable<Task<T>> Interleaved<T>(IEnumerable<Task<T>> tasks)
{
var inputTasks = tasks.ToList();
var sources = (from _ in Enumerable.Range(0, inputTasks.Count)
select new TaskCompletionSource<T>()).ToList();
int nextTaskIndex = -1;
foreach (var inputTask in inputTasks)
{
inputTask.ContinueWith(completed =>
{
var source = sources[Interlocked.Increment(ref nextTaskIndex)];
if (completed.IsFaulted)
source.TrySetException(completed.Exception!.InnerExceptions);
else if (completed.IsCanceled)
source.TrySetCanceled();
else
source.TrySetResult(completed.Result);
}, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
return from source in sources
select source.Task;
}
Public Function Interleaved(Of T)(tasks As IEnumerable(Of Task(Of T))) As IEnumerable(Of Task(Of T))
Dim inputTasks As List(Of Task(Of T)) = tasks.ToList()
Dim sources As List(Of TaskCompletionSource(Of T)) =
(From _i In Enumerable.Range(0, inputTasks.Count) Select New TaskCompletionSource(Of T)()).ToList()
Dim indexRef As Integer() = {-1}
For Each inputTask In inputTasks
inputTask.ContinueWith(Sub(completed)
Dim idx = Interlocked.Increment(indexRef(0))
Dim source = sources(idx)
If completed.IsFaulted Then
source.TrySetException(completed.Exception.InnerExceptions)
ElseIf completed.IsCanceled Then
source.TrySetCanceled()
Else
source.TrySetResult(completed.Result)
End If
End Sub,
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default)
Next
Return From source In sources Select source.Task
End Function
Görevler tamamlandıklarında sonuçlarını işlemek için birleştiriciyi kullanın. Örneğin:
IEnumerable<Task<int>> tasks = ...;
foreach(var task in Interleaved(tasks))
{
int result = await task;
…
}
WhenAllOrFirstException
Bazı dağılım/toplama senaryolarında, bunlardan biri hata oluşturmadığı sürece kümedeki tüm görevleri beklemek isteyebilirsiniz. Bu durumda, özel durum oluştuğu anda beklemeyi durdurmak istiyorsunuz. Aşağıdaki örnekte olduğu gibi WhenAllOrFirstException bir combinator yöntemi kullanarak bu davranışı gerçekleştirebilirsiniz:
public static Task<T[]> WhenAllOrFirstException<T>(IEnumerable<Task<T>> tasks)
{
var inputs = tasks.ToList();
var ce = new CountdownEvent(inputs.Count);
var tcs = new TaskCompletionSource<T[]>();
Action<Task> onCompleted = (Task completed) =>
{
if (completed.IsFaulted)
tcs.TrySetException(completed.Exception!.InnerExceptions);
if (ce.Signal() && !tcs.Task.IsCompleted)
tcs.TrySetResult(inputs.Select(t => ((Task<T>)t).Result).ToArray());
};
foreach (var t in inputs) t.ContinueWith(onCompleted);
return tcs.Task;
}
Public Function WhenAllOrFirstException(Of T)(tasks As IEnumerable(Of Task(Of T))) As Task(Of T())
Dim inputs As List(Of Task(Of T)) = tasks.ToList()
Dim ce As New CountdownEvent(inputs.Count)
Dim tcs As New TaskCompletionSource(Of T())()
Dim onCompleted As Action(Of Task) = Sub(completed As Task)
If completed.IsFaulted Then
tcs.TrySetException(completed.Exception.InnerExceptions)
End If
If ce.Signal() AndAlso Not tcs.Task.IsCompleted Then
tcs.TrySetResult(inputs.Select(Function(taskItem) DirectCast(taskItem, Task(Of T)).Result).ToArray())
End If
End Sub
For Each t In inputs
t.ContinueWith(onCompleted)
Next
Return tcs.Task
End Function
Görev tabanlı veri yapıları oluşturma
Özel görev odaklı birleştiriciler oluşturabilmenin yanı sıra, zaman uyumsuz işlemlerin sonuçlarını ve bu sonuçlarla birleşmek için gereken senkronizasyonu temsil eden Task ve Task<TResult> içerisindeki veri yapısı, zaman uyumsuz senaryolarda kullanılacak özel veri yapılarının inşasında güçlü bir temel oluşturur.
AsyncCache
Bir görevin önemli yönlerinden biri, görevi birden çok tüketiciye verebilmenizdir. Tüm tüketiciler bunu bekleyebilir, devamlılıkları kaydedebilir, sonuçlarını veya hatalarını (örneğin Task<TResult>) alabilir ve benzeri şeyler yapabilir. Bu özellik, Task ve Task<TResult>'i zaman uyumsuz bir önbelleğe alma altyapısında kullanılmak için son derece uygun hale getirir. İşte, Task<TResult> temel alınarak oluşturulmuş küçük ama güçlü bir zaman uyumsuz önbellek örneği:
public class AsyncCache<TKey, TValue> where TKey : notnull
{
private readonly Func<TKey, Task<TValue>> _valueFactory;
private readonly ConcurrentDictionary<TKey, Lazy<Task<TValue>>> _map;
public AsyncCache(Func<TKey, Task<TValue>> valueFactory)
{
if (valueFactory == null) throw new ArgumentNullException(nameof(valueFactory));
_valueFactory = valueFactory;
_map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>();
}
public Task<TValue> this[TKey key]
{
get
{
if (key == null) throw new ArgumentNullException(nameof(key));
return _map.GetOrAdd(key, toAdd =>
new Lazy<Task<TValue>>(() => _valueFactory(toAdd))).Value;
}
}
}
Public Class AsyncCache(Of TKey, TValue)
Private ReadOnly _valueFactory As Func(Of TKey, Task(Of TValue))
Private ReadOnly _map As New ConcurrentDictionary(Of TKey, Lazy(Of Task(Of TValue)))()
Public Sub New(valueFactory As Func(Of TKey, Task(Of TValue)))
If valueFactory Is Nothing Then Throw New ArgumentNullException(NameOf(valueFactory))
_valueFactory = valueFactory
End Sub
Default Public ReadOnly Property Item(key As TKey) As Task(Of TValue)
Get
If key Is Nothing Then Throw New ArgumentNullException(NameOf(key))
Return _map.GetOrAdd(key, Function(toAdd) New Lazy(Of Task(Of TValue))(Function() _valueFactory(toAdd))).Value
End Get
End Property
End Class
AsyncCache<TKey,TValue> sınıfı, oluşturucusuna bir TKey alan ve bir Task<TResult> döndüren işlevi temsilci olarak kabul eder. İç sözlük, önbellekten daha önce erişilen değerleri depolar ve AsyncCache önbellek eşzamanlı olarak erişiliyor olsa bile anahtar başına yalnızca bir görev oluşturmasını sağlar.
Örneğin, indirilen web sayfaları için bir önbellek oluşturabilirsiniz:
private AsyncCache<string,string> m_webPages =
new AsyncCache<string,string>(DownloadStringTaskAsync);
Daha sonra bir web sayfasının içeriğine ihtiyacınız olduğunda zaman uyumsuz yöntemlerle bu önbelleği kullanabilirsiniz. sınıfı mümkün AsyncCache olduğunca az sayfa indirdiğinizden emin olur ve sonuçları önbelleğe alır.
static AsyncCache<string, string> m_webPages =
new AsyncCache<string, string>(url => Stubs.DownloadStringTaskAsync(url));
public static async Task UseWebPageCache(string url)
{
string contents = await m_webPages[url];
Console.WriteLine(contents.Length);
}
Private m_webPages As New AsyncCache(Of String, String)(Function(url) Stubs.DownloadStringTaskAsync(url))
Public Async Function UseWebPageCache(url As String) As Task
Dim contents As String = Await m_webPages(url)
Console.WriteLine(contents.Length)
End Function
AsenkronÜreticiTüketiciKoleksiyonu
Zaman uyumsuz etkinlikleri koordine etmek için veri yapıları oluşturmak için görevleri de kullanabilirsiniz. Klasik paralel tasarım desenlerinden birini düşünün: üretici/tüketici. Bu düzende üreticiler tüketicilerin tükettiği verileri oluşturur ve üreticiler ve tüketiciler paralel olarak çalışabilir. Örneğin tüketici, daha önce 2. öğeyi üreten bir üretici tarafından oluşturulan 1. öğeyi işler. Üretici/tüketici düzeni için, tüketicilere yeni veriler bildirilmesi ve kullanılabilir olduğunda bulunabilmesi için üreticiler tarafından oluşturulan çalışmaları depolamak için her zaman bir veri yapısına ihtiyacınız vardır.
Aşağıda, zaman uyumsuz yöntemlerin üretici ve tüketici olarak kullanılmasını sağlayan, görevlerin üzerine kurulmuş basit bir veri yapısı bulunur:
public class AsyncProducerConsumerCollection<T>
{
private readonly Queue<T> m_collection = new Queue<T>();
private readonly Queue<TaskCompletionSource<T>> m_waiting =
new Queue<TaskCompletionSource<T>>();
public void Add(T item)
{
TaskCompletionSource<T>? tcs = null;
lock (m_collection)
{
if (m_waiting.Count > 0) tcs = m_waiting.Dequeue();
else m_collection.Enqueue(item);
}
if (tcs != null) tcs.TrySetResult(item);
}
public Task<T> Take()
{
lock (m_collection)
{
if (m_collection.Count > 0)
{
return Task.FromResult(m_collection.Dequeue());
}
else
{
var tcs = new TaskCompletionSource<T>();
m_waiting.Enqueue(tcs);
return tcs.Task;
}
}
}
}
Public Class AsyncProducerConsumerCollection(Of T)
Private ReadOnly m_collection As New Queue(Of T)()
Private ReadOnly m_waiting As New Queue(Of TaskCompletionSource(Of T))()
Public Sub Add(item As T)
Dim tcs As TaskCompletionSource(Of T) = Nothing
SyncLock m_collection
If m_waiting.Count > 0 Then
tcs = m_waiting.Dequeue()
Else
m_collection.Enqueue(item)
End If
End SyncLock
If tcs IsNot Nothing Then tcs.TrySetResult(item)
End Sub
Public Function Take() As Task(Of T)
SyncLock m_collection
If m_collection.Count > 0 Then
Return Task.FromResult(m_collection.Dequeue())
Else
Dim tcs As New TaskCompletionSource(Of T)()
m_waiting.Enqueue(tcs)
Return tcs.Task
End If
End SyncLock
End Function
End Class
Bu veri yapısıyla aşağıdaki gibi bir kod yazabilirsiniz:
static AsyncProducerConsumerCollection<int> m_data = new();
public static async Task ConsumerAsync()
{
while (true)
{
int nextItem = await m_data.Take();
Stubs.ProcessNextItem(nextItem);
}
}
public static void Produce(int data)
{
m_data.Add(data);
}
Private m_data As New AsyncProducerConsumerCollection(Of Integer)()
Public Async Function ConsumerAsync() As Task
While True
Dim nextItem As Integer = Await m_data.Take()
Stubs.ProcessNextItem(nextItem)
End While
End Function
Public Sub Produce(data As Integer)
m_data.Add(data)
End Sub
Ad alanı System.Threading.Tasks.Dataflow türünü içerir BufferBlock<T>, benzer şekilde kullanabilir, ancak özel bir koleksiyon türü oluşturmak zorunda kalmadan.
static BufferBlock<int> m_dataBlock = new();
public static async Task ConsumerAsyncBlock()
{
while (true)
{
int nextItem = await m_dataBlock.ReceiveAsync();
Stubs.ProcessNextItem(nextItem);
}
}
public static void ProduceBlock(int data)
{
m_dataBlock.Post(data);
}
Private m_dataBlock As New BufferBlock(Of Integer)()
Public Async Function ConsumerAsyncBlock() As Task
While True
Dim nextItem As Integer = Await m_dataBlock.ReceiveAsync()
Stubs.ProcessNextItem(nextItem)
End While
End Function
Public Sub ProduceBlock(data As Integer)
m_dataBlock.Post(data)
End Sub
Uyarı
Ad alanı System.Threading.Tasks.Dataflow bir NuGet paketi olarak mevcuttur. Ad alanını içeren System.Threading.Tasks.Dataflow derlemeyi yüklemek için projenizi Visual Studio'da açın, Proje menüsünden NuGet Paketlerini Yönet'i seçin ve çevrimiçi ortamda System.Threading.Tasks.Dataflow paketi arayın.