Görev tabanlı asenkron modeli uygulamak

Görev tabanlı zaman uyumsuz deseni (TAP) üç yolla uygulayabilirsiniz: C# ve Visual Basic derleyicilerini Visual Studio kullanarak, el ile veya derleyici ve el ile kullanılan yöntemlerin bir bileşimi aracılığıyla. 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, async anahtar sözcüğüyle (Async Visual Basic) ilişkilendirilen tüm yöntemler zaman uyumsuz bir yöntem olarak kabul edilir. 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.

Task.Start ve görev sonlandırma

Yalnızca, hala Start durumunda olan bir Task oluşturucuyla açıkça oluşturulan görevler için kullanınCreated. Genel TAP yöntemleri aktif görevleri döndürmelidir, bu nedenle arayanlar Start çağrısı yapma gereği duymamalıdır.

Çoğu TAP kodunda görevleri yok etmeyin. A Task , yaygın durumda yönetilmeyen kaynakları bulundurmaz ve her görevin sonlandırılması pratik bir fayda sağlamaksızın ek yük getirir. Belirli API'ler ve ölçümler bir gereksinim gösterdiğinde yalnızca imha edin.

Anlık çağrı yolunun ötesine geçen bir arka plan çalışması başlatırsanız, sahiplik durumunu belirgin tutun ve tamamlanmayı takip edin. Daha fazla bilgi için bkz. Zaman uyumsuz yöntemleri aktif tutma.

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:

static class StreamExtensions
{
    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;
    }
}
Module StreamExtensions
    <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
End Module

Karma yaklaşım

TAP desenini el ile uygulamayı ancak uygulamanın temel mantığını derleyiciye devretmeyi yararlı bulabilirsiniz. Örneğin, özel durumların System.Threading.Tasks.Task nesnesi aracılığıyla maruz bırakılmak yerine yöntemin doğrudan çağırıcısına kaçabilmesi için derleyici tarafından oluşturulan asenkron bir yöntemin dışındaki bağımsız değişkenleri doğrulamak istediğinizde karma yaklaşımı kullanmak isteyebilirsiniz.

class Calculator
{
    private int value = 0;

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

    private async Task<int> MethodAsyncInternal(string input)
    {
        // code that uses await goes here
        await Task.Delay(1);
        return value;
    }
}
Class Calculator
    Private value As Integer = 0

    Public Function MethodAsync(input As String) As Task(Of Integer)
        If input Is Nothing Then Throw New ArgumentNullException(NameOf(input))
        Return MethodAsyncInternal(input)
    End Function

    Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)
        ' code that uses await goes here
        Await Task.Delay(1)
        Return value
    End Function
End Class

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 işlemci bağımlı hem de G/Ç bağlı zaman uyumsuz işlemleri uygulayabilirsiniz. Bununla birlikte, TAP yöntemlerini bir kitaplıktan genel olarak kullanıma sunduğunuzda, bunları yalnızca G/Ç'ye bağlı işlemleri içeren iş yükleri için sağlayın. Bu işlemler hesaplama da içerebilir, ancak yalnızca hesaplama olmamalıdır. Bir yöntem tamamen işlemciye bağımlıysa, bunu yalnızca senkron olarak sunun. Bunu kullanan kod daha sonra, işi başka bir iş parçacığına boşaltmak veya paralellik elde etmek için bu zaman uyumlu yöntemin çağrısını bir göreve sarmalamayı seçebilir. Bir yöntem G/Ç bağlıysa, bunu yalnızca zaman uyumsuz bir uygulama olarak sunun.

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

sınıfı, System.Threading.Tasks.Task yoğun işlem gücü gerektiren işlemleri temsil etmek için iyi çalışır. Varsayılan olarak, verimli yürütme sağlamak için sınıfı içindeki ThreadPool özel desteklerden yararlanır. 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şturun:

  • .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 yüksek işlem gerektiren bir görevi kolayca başlatmak için Run kullanın. Bu yöntem, 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 TaskFactory.StartNew yöntemini kullanın. Zaman uyumsuz olarak yürütülecek bir temsilciyi (genellikle bir Action<T> veya bir Func<TResult>) kabul eder. 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. yönteminin StartNew aşırı yüklemeleri bir iptal belirteci (CancellationToken), görev oluşturma seçenekleri (TaskCreationOptions) ve bir görev zamanlayıcı (TaskScheduler ) kabul eder. Bu parametreler, görevin zamanlanması ve yürütülmesi üzerinde ayrıntılı denetim sağlar. 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öntemlerin yalnızca zaten başlatılmış olan görevleri döndürmesi gerekir.

  • 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. Ayrıca, belirteci StartNew veya Run gibi daha önce bahsedilen yöntemlerden birine sağlayarak, Task çalışma zamanı belirteci de izleyebilir.

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

internal static 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

Uyarı

Bu örnek, Bitmap paketini gerektiren ve yalnızca Windows'da desteklenen System.Drawing.Common kullanır. Task.Run ile CancellationToken kullanan işlem yoğun görev modeli tüm platformlarda geçerlidir; Windows olmayan hedefler için platformlar arası bir görüntü işleme kitaplığı kullanın.

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şlenmeden kalır. Bu özel durum göreve geçirilenin aynısını CancellationToken içerir ve bu belirteç iptalin istendiğini gösterir.

Görevin gövdesinde başka bir özel durum işlenmemişse, görev Faulted durumunda sona erer. Görevde beklemeye veya sonucuna erişmeye yönelik tüm denemeler bir istisna ortaya çıkarır.

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

Tam yürütme için doğrudan bir iş parçacığı kullanmaması gereken bir görev oluşturmak için TaskCompletionSource<TResult> türünü kullanın. 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ünü, TaskCompletionSource<TResult> gibi, SetResult, SetException, SetCanceled yöntemleri ve bunların TrySet varyantlarını kullanarak denetleyebilirsiniz.

Belirtilen süre sonunda tamamlanan 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. 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. Örneğin, zaman uyumsuz yoklama döngüsü uygulamak için bunu başka bir zaman uyumsuz yöntemin içinde kullanabilirsiniz:

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 bir TResult kaynağı kullanın (Boolean genellikle iyi bir varsayılan seçenektir), ancak Task'yi Task<TResult>'e dönüştüren kullanıcılarla ilgili bir endişeniz varsa, 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> DelaySimple(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 DelaySimple(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 hesaplamaya bağlı veya G/Ç'ye bağlı işlemlerle sınırlı değildir. İkisinin bir karışımını temsil edebilir. Aslında, genellikle birden çok zaman uyumsuz işlemi daha büyük karma işlemlerde birleştirirsiniz. Örneğin, önceki bir örnekteki RenderAsync yöntemi, bir görüntüyü bazı girişlere imageDatagöre işlemek için hesaplama açısından yoğun bir işlem gerçekleştirir. Bu imageData , zaman uyumsuz olarak erişdiğiniz bir web hizmetinden gelebilir:

public static 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

Uyarı

Bu örnek, Bitmap paketini gerektiren ve yalnızca Windows'da desteklenen System.Drawing.Common kullanır. Zaman uyumsuz bir indirme işlemini zaman uyumsuz hesaplama ile zincirleme düzeni tüm platformlarda geçerlidir; Windows olmayan hedefler için platformlar arası bir görüntüleme kütüphanesi kullanın.

Bu örnek ayrıca tek bir iptal belirtecinin birden çok eşzamanlı işlemle nasıl işlenebileceğini gösterir. 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