Aracılığıyla paylaş


Görev Tabanlı Zaman Uyumsuz Deseni Uygulama

Görev Tabanlı Zaman Uyumsuz Deseni (TAP) üç yolla uygulayabilirsiniz: Visual Studio'daki C# ve Visual Basic derleyicilerini el ile veya derleyici ve el ile kullanılan yöntemlerin bir bileşimi aracılığıyla kullanabilirsiniz. Aşağıdaki bölümlerde her yöntem ayrıntılı olarak açıklanmıştır. TAP desenini kullanarak hem işlemle ilişkili hem de G/Ç'ye bağlı zaman uyumsuz işlemleri uygulayabilirsiniz. İş Yükleri bölümünde her işlem türü ele alınmaktadır.

TAP yöntemleri oluşturma

Derleyicileri kullanma

.NET Framework 4.5'den başlayarak, (asyncVisual Basic'te) anahtar sözcüğüyle Async ilişkilendirilen tüm yöntemler zaman uyumsuz bir yöntem olarak kabul edilir ve C# ve Visual Basic derleyicileri, TAP kullanarak yöntemi zaman uyumsuz olarak uygulamak için gerekli dönüştürmeleri gerçekleştirir. Zaman uyumsuz bir yöntem ya System.Threading.Tasks.Task ya da System.Threading.Tasks.Task<TResult> nesnesi döndürmelidir. İkincisi için, işlevin gövdesi bir TResultdöndürmelidir ve derleyici bu sonucun sonuçta elde edilen görev nesnesi aracılığıyla kullanılabilir olmasını sağlar. Benzer şekilde, yöntemin gövdesinde işlenmeyen tüm istisnalar çıkış görevine yönlendirilir ve bu, elde edilen görevin TaskStatus.Faulted durumunda sona ermesine neden olur. Bu kuralın istisnası, bir OperationCanceledException (veya türetilmiş bir tür) işlenmediğinde, bu durumda sonuç görev TaskStatus.Canceled durumunda sona erer.

TAP yöntemlerini el ile oluşturma

Uygulama üzerinde daha iyi denetim için TAP desenini el ile uygulayabilirsiniz. Derleyici, System.Threading.Tasks ad alanından kullanıma sunulan genel alana ve System.Runtime.CompilerServices ad alanındaki destekleyici türlere dayanır. TAP'yi kendiniz uygulamak için bir TaskCompletionSource<TResult> nesne oluşturur, zaman uyumsuz işlemi gerçekleştirirsiniz ve tamamlandığında, SetResult, SetException, veya SetCanceled metodunu ya da bu yöntemlerden birinin Try sürümünü çağırırsınız. Temsil edilen asenkron işlem tamamlandığında, bir TAP yöntemini el ile uyguladığınızda elde edilen görevi tamamlamanız gerekir. Örneğin:

public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
    var tcs = new TaskCompletionSource<int>();
    stream.BeginRead(buffer, offset, count, ar =>
    {
        try { tcs.SetResult(stream.EndRead(ar)); }
        catch (Exception exc) { tcs.SetException(exc); }
    }, state);
    return tcs.Task;
}
<Extension()>
Public Function ReadTask(stream As Stream, buffer() As Byte,
                         offset As Integer, count As Integer,
                         state As Object) As Task(Of Integer)
    Dim tcs As New TaskCompletionSource(Of Integer)()
    stream.BeginRead(buffer, offset, count, Sub(ar)
                                                Try
                                                    tcs.SetResult(stream.EndRead(ar))
                                                Catch exc As Exception
                                                    tcs.SetException(exc)
                                                End Try
                                            End Sub, state)
    Return tcs.Task
End Function

Karma yaklaşım

TAP desenini el ile uygulamayı ancak uygulamanın temel mantığını derleyiciye devretmeyi yararlı bulabilirsiniz. Örneğin, derleyici tarafından oluşturulan zaman uyumsuz bir yöntemin dışındaki bağımsız değişkenleri doğrulamak istediğinizde karma yaklaşımını tercih etmek isteyebilirsiniz, böylece özel durumlar nesne üzerinden ifşa edilmek yerine doğrudan yöntemin çağırıcısına System.Threading.Tasks.Task ulaşabilir.

public Task<int> MethodAsync(string input)
{
    if (input == null) throw new ArgumentNullException("input");
    return MethodAsyncInternal(input);
}

private async Task<int> MethodAsyncInternal(string input)
{

   // code that uses await goes here

   return value;
}
Public Function MethodAsync(input As String) As Task(Of Integer)
    If input Is Nothing Then Throw New ArgumentNullException("input")

    Return MethodAsyncInternal(input)
End Function

Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)

    ' code that uses await goes here

    return value
End Function

Böyle bir temsil yetkisinin devri, hızlı yol optimizasyonunu uyguladığınızda ve önbelleğe alınmış bir görevi geri döndürmek istediğinizde yararlıdır.

İş yükleri

TAP yöntemleri olarak hem hesaplama odaklı hem de G/Ç odaklı asenkron işlemleri uygulayabilirsiniz. Ancak, TAP yöntemleri bir kitaplıktan genel olarak kullanıma sunulduğunda, bunlar yalnızca G/Ç ile ilişkili işlemleri içeren iş yükleri için sağlanmalıdır (hesaplama da içerebilir, ancak yalnızca hesaplama olmamalıdır). Bir yöntem tamamen hesaplamaya bağlıysa, yalnızca zaman uyumlu bir uygulama olarak kullanıma sunulmalıdır. Bunu kullanan kod daha sonra, işi başka bir iş parçacığına devretmek veya paralellik elde etmek için bu senkron yöntemin çağrısını bir göreve sarmalamayı seçebilir. Bir yöntem G/Ç bağlıysa, yalnızca zaman uyumsuz bir uygulama olarak kullanıma sunulmalıdır.

Hesaplama gücüne bağlı görevler

System.Threading.Tasks.Task sınıfı, işlem açısından yoğun işlemleri temsil etmek için idealdir. Varsayılan olarak, verimli yürütme sağlamak için sınıfı içindeki ThreadPool özel desteklerden yararlanır ve ayrıca zaman uyumsuz hesaplamaların ne zaman, nerede ve nasıl yürütüleceği üzerinde önemli bir denetim sağlar.

İşlemle ilişkili görevleri aşağıdaki yollarla oluşturabilirsiniz:

  • .NET Framework 4.5 ve sonraki sürümlerinde (.NET Core ve .NET 5+ dahil), statik Task.Run metodunu bir kısayol olarak TaskFactory.StartNew olarak kullanın. İş parçacığı havuzunu hedefleyen işlem bağlı bir görevi kolayca başlatmak için Run kullanabilirsiniz. Bu, işlem bağlı bir görevi başlatmak için tercih edilen mekanizmadır. Doğrudan yalnızca görev üzerinde daha ayrıntılı denetim istediğinizde kullanın StartNew .

  • .NET Framework 4'te, bir temsilciyi (genellikle bir TaskFactory.StartNew veya bir Action<T>) zaman uyumsuz olarak yürütmek üzere kabul eden Func<TResult> yöntemini kullanın. Bir Action<T> temsilci sağlarsanız, yöntemi bu temsilcinin zaman uyumsuz yürütmesini temsil eden bir System.Threading.Tasks.Task nesne döndürür. Bir Func<TResult> temsilci sağlarsanız, yöntemi bir System.Threading.Tasks.Task<TResult> nesnesi döndürür. StartNew yönteminin aşırı yüklenmeleri, ayrıntılı kontrol sağlayan bir iptal belirteci (CancellationToken), görev oluşturma seçenekleri (TaskCreationOptions) ve bir görev zamanlayıcısı (TaskScheduler) kabul eder ve bunların tümü görevin zamanlanması ve yürütülmesi üzerinde ince ayar yapma imkanı sunar. Geçerli görev zamanlayıcısını hedefleyen bir fabrika örneği, sınıfın Factory statik özelliği (Task) olarak kullanılabilir; örneğin: Task.Factory.StartNew(…).

  • Görevi ayrı olarak oluşturmak ve zamanlamak istiyorsanız Task türünün ve Start yönteminin oluşturucularını kullanın. Genel yöntemler yalnızca önceden başlatılmış görevleri döndürmelidir.

  • Task.ContinueWith metodunun aşırı yüklemelerini kullanın. Bu yöntem, başka bir görev tamamlandığında zamanlanan yeni bir görev oluşturur. Aşırı yüklemelerden bazıları, devamlılık görevinin ContinueWith zamanlanması ve yürütülmesi üzerinde daha iyi denetim için bir iptal belirteci, devamlılık seçenekleri ve bir görev zamanlayıcı kabul eder.

  • TaskFactory.ContinueWhenAll ve TaskFactory.ContinueWhenAny yöntemlerini kullanın. Bu yöntemler, sağlanan bir görev kümesinin tümü veya herhangi biri tamamlandığında zamanlanan yeni bir görev oluşturur. Bu yöntemler ayrıca bu görevlerin zamanlamasını ve yürütülmesini denetlemek için aşırı yüklemeler sağlar.

İşlem bağlantılı görevlerde sistem, görevi çalıştırmaya başlamadan önce bir iptal isteği alırsa zamanlanmış bir görevin yürütülmesini engelleyebilir. Bu nedenle, bir iptal belirteci (CancellationToken nesnesi) sağlarsanız, belirteci izleyen asenkron koda geçirebilirsiniz. Önceden belirtilen StartNew veya Run gibi yöntemlerden birine belirteci sağlayarak, Task çalışma zamanı da belirteci izleyebilir.

Örneğin, bir görüntüyü işleyen zaman uyumsuz bir yöntem düşünün. Görevin gövdesi, işleme sırasında bir iptal isteği geldiğinde kodun önceden çıkabilmesi için iptal belirtecini sorgulayabilir. Ayrıca, işleme başlamadan önce iptal isteği gelirse işleme işlemini engellemek istersiniz:

internal Task<Bitmap> RenderAsync(
              ImageData data, CancellationToken cancellationToken)
{
    return Task.Run(() =>
    {
        var bmp = new Bitmap(data.Width, data.Height);
        for(int y=0; y<data.Height; y++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            for(int x=0; x<data.Width; x++)
            {
                // render pixel [x,y] into bmp
            }
        }
        return bmp;
    }, cancellationToken);
}
Friend Function RenderAsync(data As ImageData, cancellationToken As _
                            CancellationToken) As Task(Of Bitmap)
    Return Task.Run(Function()
                        Dim bmp As New Bitmap(data.Width, data.Height)
                        For y As Integer = 0 to data.Height - 1
                            cancellationToken.ThrowIfCancellationRequested()
                            For x As Integer = 0 To data.Width - 1
                                ' render pixel [x,y] into bmp
                            Next
                        Next
                        Return bmp
                    End Function, cancellationToken)
End Function

Aşağıdaki koşullardan en az biri doğruysa, işlem bağlantılı görevler bir durumda biter Canceled :

  • İptal isteği, görev CancellationToken durumuna geçmeden önce, StartNew nesnesi aracılığıyla ve oluşturma yöntemine (örneğin, Run veya Running) bir bağımsız değişken olarak sağlanarak ulaşır.

  • Böyle bir görevin gövdesinde bir özel durum işlenmez, bu özel durum göreve geçirilen OperationCanceledException ile aynı olan bir özel durumu içerir ve bu belirteç, iptalin talep edilmekte olduğunu gösterir.

Görevin gövdesinde başka bir özel durum işlenmeden kalırsa, görev Faulted durumunda sona erer ve görevde beklemeye veya sonucuna erişmeye yönelik tüm girişimler bir özel durumun fırlatılmasına neden olur.

Girdi/Çıktı sınırlı görevler

Bir iş parçacığı ile doğrudan desteklenmemesi gereken bir görev oluşturmak için TaskCompletionSource<TResult> türü kullanılmalıdır. Bu tür, bir Task özelliği aracılığıyla ilişkili bir Task<TResult> örneği döndüren bir yapı sunar. Bu görevin yaşam döngüsü, TaskCompletionSource<TResult> gibi yöntemler, örneğin SetResult, SetException, SetCanceled ve bunların TrySet varyantları tarafından denetlenir.

Belirtilen süre sonunda tamamlanacak bir görev oluşturmak istediğinizi varsayalım. Örneğin, kullanıcı arabirimindeki bir etkinliği geciktirmek isteyebilirsiniz. System.Threading.Timer sınıfı zaten belirli bir süre sonra zaman uyumsuz olarak bir temsilci çağırma olanağı sağlar ve kullanarak TaskCompletionSource<TResult> zamanlayıcıya bir Task<TResult> ön koyabilirsiniz, örneğin:

public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
{
    TaskCompletionSource<DateTimeOffset> tcs = null;
    Timer timer = null;

    timer = new Timer(delegate
    {
        timer.Dispose();
        tcs.TrySetResult(DateTimeOffset.UtcNow);
    }, null, Timeout.Infinite, Timeout.Infinite);

    tcs = new TaskCompletionSource<DateTimeOffset>(timer);
    timer.Change(millisecondsTimeout, Timeout.Infinite);
    return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset)
    Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing
    Dim timer As Timer = Nothing

    timer = New Timer(Sub(obj)
                          timer.Dispose()
                          tcs.TrySetResult(DateTimeOffset.UtcNow)
                      End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)

    tcs = New TaskCompletionSource(Of DateTimeOffset)(timer)
    timer.Change(millisecondsTimeout, Timeout.Infinite)
    Return tcs.Task
End Function

Task.Delay yöntemi bu amaçla sağlanır ve bunu başka bir zaman uyumsuz yöntem içinde kullanabilirsiniz, örneğin, zaman uyumsuz yoklama döngüsü uygulamak için:

public static async Task Poll(Uri url, CancellationToken cancellationToken,
                              IProgress<bool> progress)
{
    while(true)
    {
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
        bool success = false;
        try
        {
            await DownloadStringAsync(url);
            success = true;
        }
        catch { /* ignore errors */ }
        progress.Report(success);
    }
}
Public Async Function Poll(url As Uri, cancellationToken As CancellationToken,
                           progress As IProgress(Of Boolean)) As Task
    Do While True
        Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)
        Dim success As Boolean = False
        Try
            await DownloadStringAsync(url)
            success = true
        Catch
            ' ignore errors
        End Try
        progress.Report(success)
    Loop
End Function

Sınıfının TaskCompletionSource<TResult> genel olmayan bir karşılığı yoktur. Ancak, Task<TResult> öğesi Task'den türetilir, bu yüzden sadece bir görev döndüren I/O ile ilişkili yöntemler için genel TaskCompletionSource<TResult> nesnesini kullanabilirsiniz. Bunu yapmak için, sahte TResult içeren bir kaynak kullanabilirsiniz (Boolean iyi bir varsayılan seçenektir, ancak Task'yi bir Task<TResult> olarak aşağı çevirecek kullanıcıdan endişeleniyorsanız, bunun yerine özel bir TResult türü kullanabilirsiniz). Örneğin, Delay önceki örnekteki yöntem, elde edilen uzaklık (Task<DateTimeOffset> ) ile birlikte geçerli saati döndürür. Böyle bir sonuç değeri gereksizse, yöntem bunun yerine aşağıdaki gibi kodlanabilir (dönüş türündeki değişikliğe ve bağımsız değişkenin TrySetResult olarak değişmesine dikkat edin):

public static Task<bool> Delay(int millisecondsTimeout)
{
     TaskCompletionSource<bool> tcs = null;
     Timer timer = null;

     timer = new Timer(delegate
     {
         timer.Dispose();
         tcs.TrySetResult(true);
     }, null, Timeout.Infinite, Timeout.Infinite);

     tcs = new TaskCompletionSource<bool>(timer);
     timer.Change(millisecondsTimeout, Timeout.Infinite);
     return tcs.Task;
}
Public Function Delay(millisecondsTimeout As Integer) As Task(Of Boolean)
    Dim tcs As TaskCompletionSource(Of Boolean) = Nothing
    Dim timer As Timer = Nothing

    Timer = new Timer(Sub(obj)
                          timer.Dispose()
                          tcs.TrySetResult(True)
                      End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)

    tcs = New TaskCompletionSource(Of Boolean)(timer)
    timer.Change(millisecondsTimeout, Timeout.Infinite)
    Return tcs.Task
End Function

İşlemle ilişkili ve G/Ç ile ilişkili karma görevler

Zaman uyumsuz yöntemler yalnızca hesaplamayla veya girdi/çıktı ile ilişkili işlemlerle sınırlı değildir, aynı zamanda ikisinin bir karışımını da temsil edebilir. Aslında, birden çok zaman uyumsuz işlem genellikle daha büyük karma işlemler halinde birleştirilir. Örneğin, önceki bir örnekteki RenderAsync yöntemi, bir görüntüyü bazı girişlere imageDatagöre işlemek için işlem açısından yoğun bir işlem gerçekleştirdi. Bu imageData , zaman uyumsuz olarak erişdiğiniz bir web hizmetinden gelebilir:

public async Task<Bitmap> DownloadDataAndRenderImageAsync(
    CancellationToken cancellationToken)
{
    var imageData = await DownloadImageDataAsync(cancellationToken);
    return await RenderAsync(imageData, cancellationToken);
}
Public Async Function DownloadDataAndRenderImageAsync(
             cancellationToken As CancellationToken) As Task(Of Bitmap)
    Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken)
    Return Await RenderAsync(imageData, cancellationToken)
End Function

Bu örnek, tek bir iptal belirtecinin birden çok zaman uyumsuz işlem aracılığıyla nasıl yürütülebileceğini de göstermektedir. Daha fazla bilgi için Görev Tabanlı Zaman Uyumsuz Deseni Kullanma bölümündeki iptal kullanımı bölümüne bakın.

Ayrıca bakınız