Çalışma Bağlamı ve Eşzamanlama Bağlamı

ve asyncile await çalışırken, iki bağlam türü önemli ama çok farklı roller oynar: ExecutionContext ve SynchronizationContext. Her birinin ne yaptığını, her birinin async/await ile nasıl etkileşim kurduğunu ve neden SynchronizationContext.Current bekleme noktaları arasında geçmediğini öğrenirsiniz.

ExecutionContext nedir?

ExecutionContext , programınızın mantıksal denetim akışıyla akan ortam durumu için bir kapsayıcıdır. Zaman uyumlu bir dünyada ortam bilgileri iş parçacığı yerel depolamasında (TLS) bulunur ve belirli bir iş parçacığında çalışan tüm kodlar bu verileri görür. Zaman uyumsuz bir dünyada, mantıksal bir işlem bir iş parçacığında başlatılabilir, askıya alınabilir ve farklı bir iş parçacığında devam ettirilebilir. İş parçacığı yerel verileri otomatik olarak takip etmez, ExecutionContext onu takip etmesini sağlar.

ExecutionContext nasıl işler?

ExecutionContext.Capture() kullanarak ExecutionContext yakalayın. ExecutionContext.Run kullanarak bir temsilcinin yürütülmesi sırasında geri yükleyin.

static void ExecutionContextCaptureDemo()
{
    // Capture the current ExecutionContext
    ExecutionContext? ec = ExecutionContext.Capture();

    // Later, run a delegate within that captured context
    if (ec is not null)
    {
        ExecutionContext.Run(ec, _ =>
        {
            // Code here sees the ambient state from the point of capture
            Console.WriteLine("Running inside captured ExecutionContext.");
        }, null);
    }
}
Sub ExecutionContextCaptureExample()
    ' Capture the current ExecutionContext
    Dim ec As ExecutionContext = ExecutionContext.Capture()

    ' Later, run a delegate within that captured context
    If ec IsNot Nothing Then
        ExecutionContext.Run(ec,
            Sub(state)
                ' Code here sees the ambient state from the point of capture
                Console.WriteLine("Running inside captured ExecutionContext.")
            End Sub, Nothing)
    End If
End Sub

Zaman uyumsuz işlemleri başlatan .NET'teki tüm API'ler (Run, QueueUserWorkItem, BeginRead ve diğerleri), ExecutionContext yakalar ve geri çağırmanızı çağırırken depolanan bağlamı kullanır. Bir iş parçacığındaki durumu yakalama ve başka bir iş parçacığında geri yükleme işlemi, "executionContext akışının" anlamıdır.

SynchronizationContext nedir?

SynchronizationContext, çalışmanın gerçekleşmesini istediğiniz hedef ortamı temsil eden bir soyutlamadır. Farklı UI çerçeveleri kendi uygulamalarını sağlar:

  • Windows Forms, Post geçersiz kılar ve Control.BeginInvoke çağırmak için WindowsFormsSynchronizationContext sağlar.
  • WPF, Post'i Dispatcher.BeginInvoke'yi çağırmak amacıyla geçersiz kılan DispatcherSynchronizationContext sağlar.
  • ASP.NET (.NET Framework'te) HttpContext.Current kullanılabilir olmasını sağlayan kendi bağlamını sağladı.

Çerçeveye özgü hazırlama API'leri yerine kullanarak SynchronizationContext , UI çerçeveleri arasında çalışan bileşenler yazabilirsiniz:

static class SyncContextExample
{
    public static void DoWork()
    {
        // Capture the current SynchronizationContext
        SynchronizationContext? sc = SynchronizationContext.Current;

        ThreadPool.QueueUserWorkItem(_ =>
        {
            // ... do work on the ThreadPool ...

            if (sc is not null)
            {
                sc.Post(_ =>
                {
                    // This runs on the original context (e.g. UI thread)
                    Console.WriteLine("Back on the original context.");
                }, null);
            }
        });
    }
}
Class SyncContextExample
    Public Shared Sub DoWork()
        ' Install a custom SynchronizationContext for demonstration
        Dim customContext As New SimpleSynchronizationContext()
        SynchronizationContext.SetSynchronizationContext(customContext)

        ' Capture the current SynchronizationContext
        Dim sc As SynchronizationContext = SynchronizationContext.Current

        ThreadPool.QueueUserWorkItem(
            Sub(state)
                ' ... do work on the ThreadPool ...

                If sc IsNot Nothing Then
                    sc.Post(
                        Sub(s)
                            ' This runs on the original context (e.g. UI thread)
                            Console.WriteLine("Back on the original context.")
                        End Sub, Nothing)
                Else
                    Console.WriteLine("No SynchronizationContext was captured.")
                End If
            End Sub)
    End Sub
End Class

' A minimal SynchronizationContext for demonstration purposes
Class SimpleSynchronizationContext
    Inherits SynchronizationContext

    Public Overrides Sub Post(d As SendOrPostCallback, state As Object)
        ' Queue the callback to run on a thread pool thread
        ThreadPool.QueueUserWorkItem(
            Sub(s)
                d(state)
            End Sub)
    End Sub
End Class

SynchronizationContext Yakalama

Bir SynchronizationContext yakaladığınızda, başvuruyu SynchronizationContext.Current okuyup daha sonra kullanmak üzere depolarsınız. Ardından yakalanan başvuru üzerinde Post çağırarak çalışmayı o ortama geri zamanlarsınız.

Flowing ExecutionContext ile SynchronizationContext'i kullanma karşılaştırması

Her iki mekanizma da bir iş parçacığından durum yakalamayı içerse de, farklı amaçlara hizmet eder:

  • ExecutionContext akışı , ortam durumunu yakalama ve temsilcinin yürütmesi sırasında aynı durumu geçerli hale getirme anlamına gelir. Temsilci, çalıştığı her yerde işlev görür; durum onu izler.
  • SynchronizationContext kullanmak, bir zamanlama hedefi yakalamak ve temsilcinin nerede çalıştırılacağına karar vermek için bunu kullanmak anlamına gelir. Yakalanan bağlam, temsilcinin nerede çalıştığını denetler.

Kısaca: ExecutionContext "Hangi ortamın görünür olması gerekir?" yanıtını verirken SynchronizationContext "kod nerede çalıştırılmalıdır?" yanıtını verir.

Asenkron/await her iki bağlamla nasıl etkileşim kurar?

Altyapı her async/await iki bağlamla da otomatik olarak ancak farklı şekillerde etkileşim kurar.

ExecutionContext her zaman devredilir

bir await yöntemi askıya alırsa (awaiter'ın IsCompleted döndürdüğü falseiçin), altyapı bir ExecutionContextyakalar. Yöntem devam ettiğinde, devam yakalanan bağlam içinde çalışır. Bu davranış zaman uyumsuz yöntem oluşturucularında yerleşik olarak bulunur (örneğin, AsyncTaskMethodBuilder) ve ne tür beklenebilir kullandığınızdan bağımsız olarak geçerlidir.

SuppressFlow() var, ancak ConfigureAwait(false) gibi beklemeye özgü bir anahtar değildir. Bastırma etkinken kuyruğa alınan işler için ExecutionContext yakalamayı engeller. Zaman uyumsuz yöntem oluşturucularına bir devamlılık için yakalanan await geri yüklemeyi atlamalarını söyleyen programlama modeli başınaExecutionContext seçeneği sağlamaz. Bu tasarım kasıtlıdır çünkü ExecutionContext, zaman uyumsuz bir dünyada iş parçacığı yerel semantiği simülasyonu sağlayan altyapı düzeyinde destektir. Çoğu geliştirici, bunun üzerinde düşünmek zorunda kalmaz.

Görev bekleyicileri SynchronizationContext'i yakalar

Task ve Task<TResult> için bekleyiciler SynchronizationContext desteği içerir. Asenkron yöntem oluşturucular bu desteği sağlamaz.

Bir görev yaptığınızda await :

  1. Awaiter SynchronizationContext.Current'yi denetler.
  2. Bir bağlam varsa, awaiter onu yakalar.
  3. Görev tamamlandığında, devam, tamamlanan iş parçacığında veya iş parçacığı havuzunda çalışmak yerine yakalanan bağlama tekrar gönderilir.

Bu davranış, await "sizi olduğunuz yere geri getirme" yöntemidir. Örneğin, bir masaüstü uygulamasında kullanıcı arabirimi iş parçacığında devam etme.

ConfigureAwait işlevi, SynchronizationContext yakalamayı kontrol eder

Eğer hazırlama davranışını istemiyorsanız, ConfigureAwait ile false çağrı yapmayın.

await task.ConfigureAwait(false);

continueOnCapturedContext öğesini false olarak ayarladığınızda, awaiter SynchronizationContext'yi denetlemez ve görev tamamlandığında (genellikle bir iş parçacığı havuzu iş parçacığında), devam çalışır. Kütüphane geliştiricileri, kodun yakalanan bağlamda devam etmesi gerekmediği sürece, her await üzerinde ConfigureAwait(false) kullanmalıdır.

SynchronizationContext.Current, beklemeler arasında akmıyor

Bu nokta en önemli noktadır: SynchronizationContext.Current bekleme noktaları arasında akış yapmaz. Çalışma zamanında eşzamansız yöntem oluşturucuları, SynchronizationContext'ın ExecutionContext'in bir parçası olarak akışını açıkça engelleyen dahili aşırı yüklemeler kullanır.

Bu neden önemli?

Teknik olarak, SynchronizationContext içerebilen alt bağlamlardan ExecutionContext biridir. bir parçası olarak ExecutionContext akıyorsa, bir iş parçacığı havuzu iş parçacığında yürütülen kod, bağlam akış aracılığıyla "sızdırıldığından" kullanıcı arabirimini SynchronizationContextCurrent olarak görebilir, bu iş parçacığı, ui iş parçacığı olduğu için değil. Bu değişiklik, SynchronizationContext.Current ifadesinin anlamını "şu anda içinde bulunduğum ortam"dan "çağrı zincirinde geçmişte bir yerde var olan ortam"a dönüştürür.

Task.Run örneği

işi iş parçacığı havuzuna aktaran kodu göz önünde bulundurun. Burada açıklanan kullanıcı arayüzü iş parçacığı davranışı, yalnızca SynchronizationContext.Current null olmadığında, yani bir kullanıcı arayüzü uygulamasında uygulanır.

static class TaskRunExample
{
    public static async Task ProcessOnUIThread()
    {
        // This method is called from a thread with a SynchronizationContext.
        // Task.Run offloads work to the thread pool.
        string result = await Task.Run(async () =>
        {
            string data = await DownloadAsync();
            // Compute runs on the thread pool, not the original context,
            // because SynchronizationContext doesn't flow into Task.Run.
            return Compute(data);
        });

        // Back on the original context (the continuation is posted back).
        Console.WriteLine(result);
    }

    private static async Task<string> DownloadAsync()
    {
        await Task.Delay(100);
        return "downloaded data";
    }

    private static string Compute(string data) =>
        $"Computed: {data.Length} chars";
}
Class TaskRunExampleClass
    Public Shared Async Function ProcessOnUIThread() As Task
        ' If a SynchronizationContext is present when this method starts,
        ' the outer await captures it. Task.Run still offloads work to the thread pool.
        Dim result As String = Await Task.Run(
            Async Function()
                Dim data As String = Await DownloadAsync()
                ' Compute runs on the thread pool, not the caller's context,
                ' because SynchronizationContext doesn't flow into Task.Run.
                Return Compute(data)
            End Function)

        ' Resume on the captured context, if one was available.
        Console.WriteLine(result)
    End Function

    Private Shared Async Function DownloadAsync() As Task(Of String)
        Await Task.Delay(100)
        Return "downloaded data"
    End Function

    Private Shared Function Compute(data As String) As String
        Return $"Computed: {data.Length} chars"
    End Function
End Class

Bir konsol uygulamasında SynchronizationContext.Current genellikle null şeklindedir, bu nedenle kod parçacığı gerçek bir kullanıcı arabirimi iş parçacığında devam etmez. Bunun yerine kod parçacığı, kuralı kavramsal olarak gösterir: Eğer bir kullanıcı arabirimi SynchronizationContext noktalar await arasında akıyorsa, await içindeki delegeye geçirilen, o kullanıcı arabirimi bağlamını Current olarak görür. Devamı await DownloadAsync() kullanıcı arayüzü iş parçacığına gönderildiğinde, Compute(data) iş parçacığı havuzu yerine UI iş parçacığında çalışmasına neden olur. Bu davranış, Task.Run çağrısının amacını bozar.

Çalışma zamanı SynchronizationContext akışını ExecutionContext içinde bastırdığından, Task.Run içindeki await dış kullanıcı arabirimi bağlamını devralmıyor ve devamlılık, beklendiği gibi iş parçacığı havuzunda çalışmayı sürdürmektedir.

Özet

Görünüş Yürütme Bağlamı SynchronizationContext
Purpose Ortam durumunu eşzamansız sınırlar arasında taşır Bir hedef zamanlayıcıyı temsil eder (burada kod çalıştırılmalıdır)
(Tarafından) Yakalandı Zaman uyumsuz yöntem oluşturucuları (altyapı) Görev bekleyenler (await task)
Akışlar arasında bekleme mi var? Evet, her zaman Hayır— yakalanan ve gönderilen, akışı yapılmayan
Gizleme API'si ExecutionContext.SuppressFlow (gelişmiş; nadiren gereklidir) ConfigureAwait(false)
Scope Tüm beklenebilir nesneler Task ve Task<TResult> (özel bekleyiciler benzer mantığı ekleyebilir)

Ayrıca bakınız