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 veya System.Threading.Tasks.TaskSystem.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övdesi içinde işlenmeyen tüm özel durumlar çıkış görevine yönlendirilir ve sonuçta elde edilen görevin durumunda bitmesini TaskStatus.Faulted sağlar. Bu kuralın istisnası, bir OperationCanceledException (veya türetilmiş tür) işlenmediğinde ve bu durumda sonuçta elde edilen görevin durumunda sona ermesidir TaskStatus.Canceled .

TAP yöntemlerini el ile oluşturma

Uygulama üzerinde daha iyi denetim için TAP desenini el ile uygulayabilirsiniz. Derleyici, ad alanından kullanıma sunulan System.Threading.Tasks genel yüzey alanına ve ad alanındaki destek türlerine System.Runtime.CompilerServices dayanır. TAP'yi kendiniz uygulamak için bir TaskCompletionSource<TResult> nesne oluşturur, zaman uyumsuz işlemi gerçekleştirirsiniz ve tamamlandığında , SetExceptionveya yöntemini ya da SetCanceledTry bu yöntemlerden birinin sürümünü çağırırsınızSetResult. Bir TAP yöntemini el ile uyguladığınızda, temsil edilen zaman uyumsuz işlem tamamlandığında sonuçta 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ı kullanmak isteyebilirsiniz, böylece özel durumlar nesne üzerinden kullanıma sunulmak yerine yöntemin doğrudan çağırıcısına System.Threading.Tasks.Task kaç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

Bu tür bir temsilci seçmenin yararlı olduğu bir diğer durum, hızlı yol iyileştirmesi uyguladığınız ve önbelleğe alınmış bir görevi döndürmek istemenizdir.

İş yükleri

TAP yöntemleri olarak hem işlem bağlı hem de G/Ç bağlı zaman uyumsuz 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 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, yalnızca zaman uyumsuz bir uygulama olarak kullanıma sunulmalıdır.

İşlemle ilişkili 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 yöntemi kısayol TaskFactory.StartNewolarak kullanın. İş parçacığı havuzunu hedefleyen bir işlem bağlı görevi kolayca başlatmak için kullanabilirsiniz Run . 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, zaman uyumsuz olarak yürütülecek bir temsilciyi (genellikle bir Action<T> veya bir Func<TResult>) kabul eden yöntemini kullanınTaskFactory.StartNew. 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 tümü görevin zamanlanması ve yürütülmesi üzerinde ayrıntılı denetim sağlayan bir görev zamanlayıcıyı (TaskScheduler ) kabul eder. Geçerli görev zamanlayıcısını hedefleyen bir fabrika örneği, sınıfın Task statik özelliği (Factory) olarak kullanılabilir; örneğin: Task.Factory.StartNew(…).

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

  • yönteminin Task.ContinueWith 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 belirteci izleyen zaman uyumsuz koda geçirebilirsiniz. Ayrıca, çalışma zamanının belirteci de izleyebilmesi için veya gibi StartNewRunTask daha önce bahsedilen yöntemlerden birine belirteci sağlayabilirsiniz.

Ö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 erken çıkabilmesi için iptal belirtecini yoklayabilir. 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 duruma geçmeden Running önce oluşturma yöntemine (örneğin veya RunStartNew ) bağımsız değişken olarak sağlanan nesnesi aracılığıyla CancellationToken ulaşır.

  • Böyle OperationCanceledException bir görevin gövdesinde bir özel durum işlenmemiştir, 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 durumunda sona erer Faulted ve görevde beklemeye veya sonucuna erişmeye yönelik tüm girişimler bir özel durum oluşturmasına neden olur.

G/Ç ilişkili görevler

Bir iş parçacığı tarafından yürütülmesinin tamamı için doğrudan yedeklenmemesi gereken bir görev oluşturmak için türünü kullanın TaskCompletionSource<TResult> . Bu tür, ilişkili Task<TResult> bir Task örneği döndüren bir özelliği kullanıma sunar. Bu görevin yaşam döngüsü, , SetException, SetCanceledve bunların varyantları TrySet gibi SetResultyöntemler tarafından TaskCompletionSource<TResult> 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> öğesinden Tasktüretilir, böylece yalnızca bir görev döndüren G/Ç ile ilişkili yöntemler için genel TaskCompletionSource<TResult> nesneyi kullanabilirsiniz. Bunu yapmak için, manken TResult içeren bir kaynak kullanabilirsiniz (Boolean iyi bir varsayılan seçenektir, ancak bunu bir 'e Task<TResult>indiren kullanıcıyla Task ilgileniyorsanız, bunun yerine özel TResult bir 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öntemi bunun yerine aşağıdaki gibi kodlanabilir (dönüş türü değişikliğine ve bağımsız değişkenin olarak değiştirilmesine TrySetResultdikkat 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 işlemle veya G/Ç ile ilişkili işlemlerle sınırlı değildir, aynı zamanda ikisinin bir karışımını 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 örnekte gösterilen RenderAsync yöntemi, bazı giriş imageData verilerine göre bir resmi işlemek için yoğun bir hesaplama 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 örnekte, tek bir iptal belirtecinin birden çok zaman uyumsuz işlem aracılığıyla nasıl iş parçacıklı olabileceği de gösterilmektedir. 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 bkz.