Görev Tabanlı Zaman Uyumsuz Desen Kullanma

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. Görevler için bu, gibi Task.ContinueWithyöntemlerle elde edilir. 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

ve Task<TResult> nesnelerini zaman uyumsuz olarak beklemek Task için C# dilinde await anahtar sözcüğünü ve Visual Basic'teki Await İşlecini kullanabilirsiniz. bir Taskawait beklediğiniz zaman ifadesi türündedirvoid. bir Task<TResult>await beklediğiniz zaman ifadesi türündedirTResult. bir await ifade, zaman uyumsuz bir yöntemin gövdesinde gerçekleşmelidir. (Bu dil özellikleri .NET Framework 4.5'te kullanıma sunulmuştur.)

Kapakların altında await işlevi, bir devamlılık kullanarak göreve bir geri çağırma yükler. Bu geri çağırma, askıya alma noktasında zaman uyumsuz 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>TResult ise döndürülür. Task beklenen veya Task<TResult> durumunda sona erdiyseCanceled, bir OperationCanceledException özel durum oluşturulur. Task beklenen veya Task<TResult> durumunda sona erdiyseFaulted, hataya neden olan özel durum oluşturulur. birden Task çok özel durumun sonucu olarak hata verebilir, ancak bu özel durumlardan yalnızca biri yayı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ığı yerden devam edip etmeyeceğini veya sürdürmenin zamanlanıp zamanlanmayacağını 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öntem çağrıldığında, işlevin gövdesini henüz tamamlanmamış bir awaitable örneğinde ilk await ifadesine kadar zaman uyumlu bir şekilde yürütür ve bu noktada çağrı çağırana döner. Zaman uyumsuz yöntem döndürmezse void, devam eden hesaplamayı temsil etmek için bir Task veya Task<TResult> nesnesi döndürülür. Geçersiz olmayan zaman uyumsuz bir yöntemde, bir dönüş deyimiyle karşılaşılırsa veya yöntem gövdesinin sonuna ulaşılırsa, görev son durumda tamamlanır RanToCompletion . İşlenmeyen bir özel durum denetimin zaman uyumsuz yöntemin gövdesinden çıkmasına neden olursa, görev durumunda biter Faulted . Bu özel durum bir OperationCanceledExceptionise, bunun yerine görev durumunda biter Canceled . Bu şekilde sonuç veya özel durum sonunda yayımlanır.

Bu davranışın birkaç önemli varyasyonu vardır. Performans nedenleriyle, görev beklenene kadar bir görev zaten tamamlandıysa, denetim gösterilmez 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, sonraki bölümde daha ayrıntılı olarak açıklanmıştır.

Verim 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, zaman uyumsuz yönteme bir verim noktası eklemek için yöntemini kullanabilirsiniz Task.Yield :

public class Task : …
{
    public static YieldAwaitable Yield();
    …
}

Bu, geçerli bağlama zaman uyumsuz olarak deftere nakil veya zamanlama ile eşdeğerdir.

Task.Run(async delegate
{
    for(int i=0; i<1000000; i++)
    {
        await Task.Yield(); // fork the continuation into a separate work item
        ...
    }
});

Zaman uyumsuz bir yöntemde Task.ConfigureAwait askıya alma ve yeniden verme üzerinde daha iyi denetim için 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, devam bağlamını önemsemeyebilir ve bu tür gönderileri özgün bağlama geri döndürerek daha iyi bir performans elde edebilirsiniz. Bunu etkinleştirmek için yöntemini kullanarak Task.ConfigureAwait await işlemine bağlamı yakalayıp sürdürmesini değil, beklenen zaman uyumsuz işlemin tamamlandığı yerde yürütmeye devam etmesini sağlayın:

await someTask.ConfigureAwait(continueOnCapturedContext:false);

Zaman Uyumsuz İşlemi İptal 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, bir iptal belirteci kaynağı (CancellationTokenSource nesnesi) aracılığıyla oluşturulur. Kaynağın Token özelliği, kaynağın yöntemi çağrıldığında sinyal verilecek iptal belirtecini Cancel döndürür. Örneğin, tek bir web sayfasını indirmek ve işlemi iptal edebilmek istiyorsanız, bir CancellationTokenSource nesne oluşturur, belirtecini TAP yöntemine geçirir ve işlemi iptal etmeye hazır olduğunuzda kaynağın Cancel yöntemini çağırırsınız:

var cts = new CancellationTokenSource();
string result = await DownloadStringTaskAsync(url, cts.Token);
… // at some point later, potentially on another thread
cts.Cancel();

Birden çok zaman uyumsuz çağrıyı iptal etmek için, tüm çağrılara aynı belirteci geçirebilirsiniz:

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

İptal istekleri herhangi bir iş parçacığından başlatılabilir.

İptalin CancellationToken.None hiçbir zaman istenmeyeceğini belirtmek için değerini iptal belirteci kabul eden herhangi bir yönteme geçirebilirsiniz. Bu, özelliğinin CancellationToken.CanBeCanceled döndürmesine falseneden olur ve çağrılan yöntem uygun şekilde iyileştirebilir. 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, herhangi bir sayıda dinleyiciye dağıtılabilir.

  • Zaman uyumsuz API'nin geliştiricisi, iptalin istenip istenmeyeceği ve ne zaman geçerli olabileceği konusunda tam denetime sahiptir.

  • API'yi kullanan kod, iptal isteklerinin yayılacağı zaman uyumsuz çağrıları seçmeli olarak belirleyebilir.

İlerlemeyi İzleme

Bazı zaman uyumsuz yöntemler, zaman uyumsuz yönteme geçirilen bir ilerleme arabirimi aracılığıyla ilerleme durumunu kullanıma sunar. Ö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öntem bir Windows Presentation Foundation (WPF) uygulamasında aşağıdaki gibi kullanılabilir:

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ı Kombinatörleri Kullanma

Ad System.Threading.Tasks alanı, görevleri oluşturmak ve görevlerle çalışmak için çeşitli yöntemler içerir.

Task.Run

sınıfı, Task iş parçacığı havuzuna veya Task<TResult> olarak Task kolayca iş yükünü boşaltmanıza olanak sağlayan çeşitli Run yöntemler içerir, örneğin:

public async void button1_Click(object sender, EventArgs e)
{
    textBox1.Text = await Task.Run(() =>
    {
        // … do compute-bound work here
        return answer;
    });
}

Aşırı yükleme gibi Task.Run(Func<Task>) bu Run yöntemlerden bazıları yöntemin kısaltması TaskFactory.StartNew olarak mevcuttur. Bu aşırı yükleme, boşaltılan iş içinde await'yi kullanmanıza olanak tanır, örneğin:

public async void button1_Click(object sender, EventArgs e)
{
    pictureBox1.Image = await Task.Run(async() =>
    {
        using(Bitmap bmp1 = await DownloadFirstImageAsync())
        using(Bitmap bmp2 = await DownloadSecondImageAsync())
        return Mashup(bmp1, bmp2);
    });
}

Bu tür aşırı yüklemeler mantıksal olarak, Yöntemini Görev Paralel Kitaplığı'ndaki uzantı yöntemiyle Unwrap birlikte kullanmaya TaskFactory.StartNew eşdeğerdir.

Task.FromResult

FromResult Verilerin zaten kullanılabilir olabileceği ve yalnızca bir içine kaldırılan Task<TResult>bir görev döndüren yöntemden döndürülmesi gereken senaryolarda yöntemini kullanın:

public Task<int> GetValueAsync(string key)
{
    int cachedValue;
    return TryGetCachedValue(out cachedValue) ?
        Task.FromResult(cachedValue) :
        GetValueAsyncInternal();
}

private async Task<int> GetValueAsyncInternal(string key)
{
    …
}

Task.WhenAll

WhenAll Görev olarak temsil edilen birden çok zaman uyumsuz işlemde zaman uyumsuz olarak beklemek için yöntemini kullanın. yöntemi, genel olmayan görevleri veya tekdüzen olmayan genel görevleri 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 çakışarak bir sonraki iletiyi göndermeden önce bir iletinin tamamlanmasını beklemeyebilirsiniz. Ayrıca gönderme işlemlerinin ne zaman tamamlandığı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 ' dan elde edilen görevde 'den awaitWhenAllyayılmasına izin verir. Özel durumları işlemek için aşağıdaki gibi bir kod kullanabilirsiniz:

IEnumerable<Task> asyncOps = from addr in addrs select SendMailAsync(addr);
try
{
    await Task.WhenAll(asyncOps);
}
catch(Exception exc)
{
    ...
}

Bu durumda, herhangi bir zaman uyumsuz işlem başarısız olursa, tüm özel durumlar yönteminden WhenAll döndürülen içinde Task depolanan bir AggregateException özel durumda bir araya gelir. Ancak, bu özel durumlardan yalnızca biri anahtar sözcüğüyle await yayılır. Tüm özel durumları incelemek istiyorsanız, önceki kodu aşağıdaki gibi yeniden yazabilirsiniz:

Task [] asyncOps = (from addr in addrs select SendMailAsync(addr)).ToArray();
try
{
    await Task.WhenAll(asyncOps);
}
catch(Exception exc)
{
    foreach(Task faulted in asyncOps.Where(t => t.IsFaulted))
    {
        … // work with faulted and faulted.Exception
    }
}

Web'den zaman uyumsuz olarak birden çok dosya indirme örneğini ele alalım. 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 ele aldığımız özel durum işleme tekniklerinin aynısını kullanabilirsiniz:

Task<string> [] asyncOps =
    (from url in urls select DownloadStringTaskAsync(url)).ToArray();
try
{
    string [] pages = await Task.WhenAll(asyncOps);
    ...
}
catch(Exception exc)
{
    foreach(Task<string> faulted in asyncOps.Where(t => t.IsFaulted))
    {
        … // work with faulted and faulted.Exception
    }
}

Task.WhenAny

Zaman uyumsuz olarak görev olarak temsil edilen birden çok zaman uyumsuz işlemden yalnızca birinin tamamlanmasını beklemek için yöntemini kullanabilirsiniz WhenAny . 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ç oluşturacak 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.

  • Azaltma: Başkaları tamamlandıktan sonra ek işlemlerin başlamasına izin verme. Bu, araya ekleme senaryosunun bir uzantısıdır.

  • Erken kurtarma: Örneğin, t1 göreviyle temsil edilen bir işlem başka bir t2 görevine sahip bir WhenAny görevde gruplandırılabilir ve görevi bekleyebilirsiniz WhenAny . T2 görevi zaman aşımını veya iptali ya da t1 tamamlanmadan önce görevin tamamlanmasına WhenAny 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 birkaç stok önerisi web hizmeti vardır, ancak günlük yüke bağlı olarak her hizmet farklı zamanlarda yavaş olabilir. Herhangi bir işlem tamamlandığında bildirim almak için yöntemini kullanabilirsiniz WhenAny :

var recommendations = new List<Task<bool>>()
{
    GetBuyRecommendation1Async(symbol),
    GetBuyRecommendation2Async(symbol),
    GetBuyRecommendation3Async(symbol)
};
Task<bool> recommendation = await Task.WhenAny(recommendations);
if (await recommendation) BuyStock(symbol);

'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ızdan, döndürülen görevin hataların yayılmasını ve try/catch bunların uygun şekilde yayılmasını bekleyebilirsiniz; örneğin:

Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{
    Task<bool> recommendation = await Task.WhenAny(recommendations);
    try
    {
        if (await recommendation) BuyStock(symbol);
        break;
    }
    catch(WebException exc)
    {
        recommendations.Remove(recommendation);
    }
}

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örevler tamamlanana kadar bekleyebilirsiniz; bu durumda yöntemini kullanabilir WhenAll veya tüm özel durumların önemli olduğuna ve günlüğe kaydedilmesi gerektiğine karar vekleyebilirsiniz. Bunun için, 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) { Log(exc); }
    }
}
…
LogCompletionIfFailed(recommendations);

Son olarak, kalan tüm işlemleri iptal etmek isteyebilirsiniz:

var cts = new CancellationTokenSource();
var recommendations = new List<Task<bool>>()
{
    GetBuyRecommendation1Async(symbol, cts.Token),
    GetBuyRecommendation2Async(symbol, cts.Token),
    GetBuyRecommendation3Async(symbol, cts.Token)
};

Task<bool> recommendation = await Task.WhenAny(recommendations);
cts.Cancel();
if (await recommendation) BuyStock(symbol);

Aralaması

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 eklemek istersiniz.

List<Task<Bitmap>> imageTasks =
    (from imageUrl in urls select GetBitmapAsync(imageUrl)).ToList();
while(imageTasks.Count > 0)
{
    try
    {
        Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
        imageTasks.Remove(imageTask);

        Bitmap image = await imageTask;
        panel.AddImage(image);
    }
    catch{}
}

ayrıca, indirilen görüntülerin üzerinde yoğun işlem gerektiren bir senaryoya ThreadPool da araya ekleme uygulayabilirsiniz; örneğin:

List<Task<Bitmap>> imageTasks =
    (from imageUrl in urls select GetBitmapAsync(imageUrl)
         .ContinueWith(t => ConvertImage(t.Result)).ToList();
while(imageTasks.Count > 0)
{
    try
    {
        Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
        imageTasks.Remove(imageTask);

        Bitmap image = await imageTask;
        panel.AddImage(image);
    }
    catch{}
}

Azaltma

Kullanıcının indirmelerin kısıtlanması gereken kadar çok görüntü indirmesi dışında, araya ekleme örneğini göz önünde bulundurun; örneğin, yalnızca belirli sayıda indirmenin eşzamanlı olarak gerçekleşmesini istiyorsunuz. Bunu başarmak için zaman uyumsuz işlemlerin bir alt kümesini başlatabilirsiniz. İşlemler tamamlandıktan sonra, yerlerine geçmek için ek işlemler başlatabilirsiniz:

const int CONCURRENCY_LEVEL = 15;
Uri [] urls = …;
int nextIndex = 0;
var imageTasks = new List<Task<Bitmap>>();
while(nextIndex < CONCURRENCY_LEVEL && nextIndex < urls.Length)
{
    imageTasks.Add(GetBitmapAsync(urls[nextIndex]));
    nextIndex++;
}

while(imageTasks.Count > 0)
{
    try
    {
        Task<Bitmap> imageTask = await Task.WhenAny(imageTasks);
        imageTasks.Remove(imageTask);

        Bitmap image = await imageTask;
        panel.AddImage(image);
    }
    catch(Exception exc) { Log(exc); }

    if (nextIndex < urls.Length)
    {
        imageTasks.Add(GetBitmapAsync(urls[nextIndex]));
        nextIndex++;
    }
}

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:

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();
    btnRun.Enabled = false;
    try
    {
        Task<Bitmap> imageDownload = GetBitmapAsync(txtUrl.Text);
        await UntilCompletionOrCancellation(imageDownload, m_cts.Token);
        if (imageDownload.IsCompleted)
        {
            Bitmap image = await imageDownload;
            panel.AddImage(image);
        }
        else imageDownload.ContinueWith(t => Log(t));
    }
    finally { btnRun.Enabled = true; }
}

private static async Task UntilCompletionOrCancellation(
    Task asyncOp, CancellationToken ct)
{
    var tcs = new TaskCompletionSource<bool>();
    using(ct.Register(() => tcs.TrySetResult(true)))
        await Task.WhenAny(asyncOp, tcs.Task);
    return asyncOp;
}

Bu uygulama, siz kurtarma yapmaya karar verir vermez kullanıcı arabirimini yeniden etkinleştirir, ancak temel alınan zaman uyumsuz işlemleri iptal etmez. Bir diğer alternatif de, kurtarmayı kaldırmaya karar vererek bekleyen işlemleri iptal etmek, ancak işlem tamamlanana kadar kullanıcı arabirimini yeniden oluşturmamak ve iptal isteği nedeniyle erken bitme olasılığı olabilir:

private CancellationTokenSource m_cts;

public async void btnRun_Click(object sender, EventArgs e)
{
    m_cts = new CancellationTokenSource();

    btnRun.Enabled = false;
    try
    {
        Task<Bitmap> imageDownload = GetBitmapAsync(txtUrl.Text, m_cts.Token);
        await UntilCompletionOrCancellation(imageDownload, m_cts.Token);
        Bitmap image = await imageDownload;
        panel.AddImage(image);
    }
    catch(OperationCanceledException) {}
    finally { btnRun.Enabled = true; }
}

Erken kurtarmanın bir diğer örneği, sonraki bölümde açıklandığı gibi yöntemi yöntemle Delay birlikte kullanmayı WhenAny içerir.

Task.Delay

Zaman uyumsuz bir yöntemin Task.Delay yürütmesine duraklamalar eklemek için yöntemini kullanabilirsiniz. Bu, yoklama döngüleri oluşturma ve kullanıcı girişinin işlenmesini önceden belirlenmiş bir süre boyunca geciktirme gibi birçok işlev türü için kullanışlıdır. yöntemi, Task.Delay awaits üzerinde zaman aşımları uygulamak için ile Task.WhenAny birlikte de yararlı olabilir.

Daha büyük bir zaman uyumsuz işlemin parçası olan bir görevin (örneğin, ASP.NET bir web hizmeti) tamamlanması çok uzun sürerse, özellikle de tamamlanamadığında genel işlem zarar görür. Bu nedenle, zaman uyumsuz bir işlem beklerken zaman aşımına uğradık. Zaman uyumlu , ve yöntemleri zaman aşımı değerlerini kabul eder, ancak karşılık gelen TaskFactory.ContinueWhenAll/TaskFactory.ContinueWhenAny ve daha önce bahsedilenTask.WhenAny/Task.WhenAll yöntemler kabul etmemektedir.Task.WaitAnyTask.WaitAllTask.Wait Bunun yerine, zaman aşımı uygulamak için ve'yi Task.WhenAny birlikte kullanabilirsinizTask.Delay.

Örneğin, kullanıcı arabirimi uygulamanızda bir görüntü indirmek 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 async void btnDownload_Click(object sender, EventArgs e)
{
    btnDownload.Enabled = false;
    try
    {
        Task<Bitmap> download = GetBitmapAsync(url);
        if (download == await Task.WhenAny(download, Task.Delay(3000)))
        {
            Bitmap bmp = await download;
            pictureBox.Image = bmp;
            status.Text = "Downloaded";
        }
        else
        {
            pictureBox.Image = null;
            status.Text = "Timed out";
            var ignored = download.ContinueWith(
                t => Trace("Task finally completed"));
        }
    }
    finally { btnDownload.Enabled = true; }
}

Aynı durum, bir görev döndürdüğünden WhenAll birden çok indirme için de geçerlidir:

public async void btnDownload_Click(object sender, RoutedEventArgs e)
{
    btnDownload.Enabled = false;
    try
    {
        Task<Bitmap[]> downloads =
            Task.WhenAll(from url in urls select GetBitmapAsync(url));
        if (downloads == await Task.WhenAny(downloads, Task.Delay(3000)))
        {
            foreach(var bmp in downloads.Result) panel.AddImage(bmp);
            status.Text = "Downloaded";
        }
        else
        {
            status.Text = "Timed out";
            downloads.ContinueWith(t => Log(t));
        }
    }
    finally { btnDownload.Enabled = true; }
}

Görev Tabanlı Birleştiriciler Oluşturma

Bir görev zaman uyumsuz bir işlemi tamamen temsil edebildiğinden ve işlemle katılmak, sonuçlarını almak vb. için zaman uyumlu ve zaman uyumsuz özellikler sağlayabildiğinden, daha büyük desenler oluşturmak için görevleri oluşturan 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 isteyebilirsiniz. Zaman uyumlu kod için, bunu 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);
}

TAP ile uygulanan ve dolayısıyla görevleri döndüren zaman uyumsuz işlemler için neredeyse aynı yardımcı yöntemi 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);
}

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 yeniden denemeler arasında çağrılacak başka bir Func<Task> işlemi kabul edebilir; örneğin:

public static async Task<T> RetryOnFault<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);
}

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(
    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 => Log(t), TaskContinuationOptions.OnlyOnFaulted);
    }
    return completed;
}

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

Araya Kaydedilen İşlemler

Büyük görev kümeleriyle çalışırken araya ekleme senaryolarını WhenAny desteklemek için yöntemini kullanmayla ilgili olası bir performans sorunu vardır. Her çağrı, WhenAny her göreve bir devamlılık kaydedilmesiyle sonuçlanıyor. N görev sayısı için bu, araya ekleme işleminin ömrü boyunca oluşturulan O(N2) devamlılıklarına neden olur. Büyük bir görev kümesiyle çalışıyorsanız, performans sorununu çözmek için bir birleştirici (Interleaved aşağıdaki örnekte) kullanabilirsiniz:

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

Ardından, görevlerin sonuçlarını tamamlandıklarında işlemek için birleştiriciyi kullanabilirsiniz; örneğin:

IEnumerable<Task<int>> tasks = ...;
foreach(var task in Interleaved(tasks))
{
    int result = await task;
    …
}

WhenAllOrFirstException

Bazı dağılım/toplama senaryolarında, hatalardan biri olmadığı sürece kümedeki tüm görevleri beklemek isteyebilirsiniz. Bu durumda, özel durum oluştuğu anda beklemeyi durdurmak isteyebilirsiniz. Bunu aşağıdaki örnekte olduğu gibi WhenAllOrFirstException bir combinator yöntemiyle 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 => t.Result).ToArray());
    };

    foreach (var t in inputs) t.ContinueWith(onCompleted);
    return tcs.Task;
}

Görev Tabanlı Veri Yapıları Oluşturma

Özel görev tabanlı birleştiriciler oluşturma özelliğine ek olarak, içinde veri yapısına Task sahip olmak ve Task<TResult> hem zaman uyumsuz bir işlemin sonuçlarını hem de onunla birleştirmek için gerekli eşitlemeyi temsil eder, bunu zaman uyumsuz senaryolarda kullanılacak özel veri yapılarının oluşturulacağı güçlü bir tür haline getirir.

AsyncCache

Bir görevin önemli özelliklerinden biri, birden çok tüketiciye dağıtılabilmesi, bunların tümünün bunu bekleyebilmesi, devamlılıkları kaydedebilmesi, sonucunu veya özel durumlarını (örneğin) alabilmesi ve bu şekilde devam edebilmesidir Task<TResult>. Bu, Task zaman uyumsuz bir önbelleğe alma altyapısında kullanılmak için son derece uygundur ve Task<TResult> uygundur. Aşağıda, üzerinde oluşturulan küçük ama güçlü bir zaman uyumsuz önbellek örneği verilmiştir Task<TResult>:

public class AsyncCache<TKey, TValue>
{
    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("valueFactory");
        _valueFactory = valueFactory;
        _map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>();
    }

    public Task<TValue> this[TKey key]
    {
        get
        {
            if (key == null) throw new ArgumentNullException("key");
            return _map.GetOrAdd(key, toAdd =>
                new Lazy<Task<TValue>>(() => _valueFactory(toAdd))).Value;
        }
    }
}

AsyncCache<TKey,TValue> sınıfı oluşturucusunun bir ve döndüren Task<TResult>TKey bir işlevinin temsilcisi olarak kabul eder. Önbellekten daha önce erişilen tüm değerler iç sözlükte depolanır ve AsyncCache önbellek eşzamanlı olarak erişiliyor olsa bile anahtar başına yalnızca bir görevin oluşturulması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.

private async void btnDownload_Click(object sender, RoutedEventArgs e)
{
    btnDownload.IsEnabled = false;
    try
    {
        txtContents.Text = await m_webPages["https://www.microsoft.com"];
    }
    finally { btnDownload.IsEnabled = true; }
}

AsyncProducerConsumerCollection

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üketiciler tarafından tüketilen 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 sürekli olarak bazı 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;
            }
        }
    }
}

Bu veri yapısıyla aşağıdaki gibi bir kod yazabilirsiniz:

private static AsyncProducerConsumerCollection<int> m_data = …;
…
private static async Task ConsumerAsync()
{
    while(true)
    {
        int nextItem = await m_data.Take();
        ProcessNextItem(nextItem);
    }
}
…
private static void Produce(int data)
{
    m_data.Add(data);
}

Ad System.Threading.Tasks.Dataflow alanı, benzer şekilde kullanabileceğiniz ancak özel bir koleksiyon türü oluşturmak zorunda kalmadan türünü içerir BufferBlock<T> :

private static BufferBlock<int> m_data = …;
…
private static async Task ConsumerAsync()
{
    while(true)
    {
        int nextItem = await m_data.ReceiveAsync();
        ProcessNextItem(nextItem);
    }
}
…
private static void Produce(int data)
{
    m_data.Post(data);
}

Not

Ad System.Threading.Tasks.Dataflow alanı NuGet paketi olarak kullanılabilir. 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.

Ayrıca bkz.