Soru Async yönteminde bekleyen kilitlenmeyi önlemek için Task.Run () yöntemini senkronize yöntemde kullanın.


GÜNCELLEŞTİRME Bu sorunun amacı basit bir cevap almaktır. Task.Run() ve kilitlenme. Eşzamansız ve senkronize edilmemenin teorik mantığını çok iyi anlıyorum ve onları kalbe alıyorum. Ben başkalarından yeni şeyler öğrenmek yukarıda değilim; Yapabildiğim zaman bunu yapmaya çalışıyorum. Bir erkeğin ihtiyaç duyduğu teknik bir cevap olduğunda ...

Benim bir Dispose() async yöntemini çağırması gereken yöntem. Kodumun% 95'i uyumsuz olduğundan, yeniden düzenleme en iyi seçim değildir. Sahip olmak IAsyncDisposable Çerçeve tarafından desteklenen diğer özellikler arasında ideal olabilir, ancak henüz orada değiliz. Yani, bu arada, zaman uyumsuz olarak senkronize bir yöntemden async yöntemlerini aramak için güvenilir bir yol bulmak gerekir.

Tercih ederim değil kullanmak ConfigureAwait(false) Çünkü bu, arayanın senkronize olması durumunda, arayanın kodun tamamı boyunca dağınıklık hissinin tamamen belli bir şekilde davranmasını sağlar. Sapkın bugger olduğundan senkronize yöntemde bir şeyler yapmayı tercih ederim.

Stephen Cleary'nin başka bir sorudaki yorumunu okuduktan sonra Task.Run() her zaman iş parçacığı havuzu üzerinde zaman uyumsuz yöntemleri, zamanlamaları beni düşünür.

ASP.NET'te .NET 4.5'te veya eşzamansız bir yöntem varsa, geçerli iş parçacığı / aynı iş parçacığıyla görevleri zamanlayan başka bir eşitleme bağlamında:

private async Task MyAsyncMethod()
{
    ...
}

Ve bunu senkron bir yöntemden aramak istiyorum, sadece kullanabilir miyim Task.Run() ile Wait() kilitlenme önlemek için async yöntemi iş parçacığı havuzu kuyruk?

private void MySynchronousMethodLikeDisposeForExample()
{
    // MyAsyncMethod will get queued to the thread pool 
    // so it shouldn't deadlock with the Wait() ??
    Task.Run((Func<Task>)MyAsyncMethod).Wait();
}

32
2018-02-03 18:18


Menşei


Eğer MyAsyncMethod() çağrı bağlamına geri dönüyor, muhtemelen o bağlamda yürütülmesi gerekiyor ya da işe yaramıyor. Tamamen bu problemden kaçınmalısınız. Ya program senkronize olun ya da asenkronize olun. Yarım ve yarısı yapmak sadece sorunlara neden olur. - Servy
Kaynakları eşzamansız olarak temizler misiniz? Bu biraz şüpheli görünüyor. Temizleme, asenkron çalışma gerektirecek bir şey olmamalı. - Servy
İki kere yapman gerektiğini söylemiyorum. IO yapmamalısınız, ya da başka bir iş parçacığına ya da ilk etapta (ya da bu tür operasyonları oluşturan herhangi bir operasyonda) asenkronize olmak için gerekçeleri olan diğer operasyon türlerine boşaltılan işler yapmamalısınız. Bu tip pahalı operasyonları eşzamanlı olarak yapma fikri Dispose yöntem en azından ilgili. - Servy
Ve nihayetinde, bu sorunu çözmek için kullanabileceğiniz her olası yaklaşım daha fazla soruna yol açacaktır (en azından belirli durumlarda). Tek yolu Gerçekten mi sorunu daha fazla sormadan çözmek, ilk etapta meydana gelmesini önlemek. - Servy
Evet ve eşzamansız olarak eşzamanlı olarak çağırmamız gereken tek yer, orantısız sayıda soruna neden oldu. Eğer çerçeve inşa ediyorsanız, neyin kullanıldığını / etrafından geçtiğini kontrol edersiniz. Ancak, her zaman bağlamı geçmek için tekrar tekrar kullanılamayan kod çabası gerekli değildir. Kurallar (geçerli UI / ASP.Net içeriğiyle çalıştırın) her türlü rastgele sorunla karşılaşırsınız - yanlış kültür, eksik HttpContext.CurrentUI güncellemelerini havaya uçurmak ... - Alexei Levenkov


Cevaplar:


Sorunuzda yer alan riskleri anlıyorsunuz, bu yüzden dersi atlayacağım.

Asıl sorunuzu cevaplamak için: Evet, sadece kullanabilirsiniz Task.Run bu işi bir ThreadPool olmayan bir iş parçacığı SynchronizationContext ve böylece bir kilitlenme için gerçek bir risk yoktur.

ancakBaşka bir iş parçacığı kullanarak sadece SC'si olmadığı için bir parça kesmek ve bu işin planlandığı zamandan beri pahalı bir yöntem olabilir. ThreadPool masrafları vardır.

Daha iyi ve daha açık bir çözüm IMO, kullanmak için SC'yi basitçe kaldırmak olacaktır. SynchronizationContext.SetSynchronizationContext ve daha sonra geri yükleme. Bu kolayca bir içine kapsüllenebilir IDisposable böylece onu bir using kapsam:

public static class NoSynchronizationContextScope
{
    public static Disposable Enter()
    {
        var context = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(null);
        return new Disposable(context);
    }

    public struct Disposable : IDisposable
    {
        private readonly SynchronizationContext _synchronizationContext;

        public Disposable(SynchronizationContext synchronizationContext)
        {
            _synchronizationContext = synchronizationContext;
        }

        public void Dispose() =>
            SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
    }
}

Kullanımı:

private void MySynchronousMethodLikeDisposeForExample()
{
    using (NoSynchronizationContextScope.Enter())
    {
        MyAsyncMethod().Wait();
    }
}

47
2018-02-03 20:17



Hmmmm ..... Bunu sevdim. Bunu yapmam gereken yerler en az Application_End() Bir ASP.NET uygulamasında ve iş parçacığı havuzu yükü muhtemelen önemli bir faktör değildir, ancak bu yaklaşım kesinlikle daha doğrudan ve temizdir. Cevabınız sadece bir yönünü oluşturdu SynchronizationContextde tıklayın. - MikeJansen
@MikeJansen Hayır. Her iki durumda da SC yoktur, yani ilk önce kod await içinde MyAsyncMethod iş parçacığı havuzuna gönderilir, ancak daha önce kodu bekler (eşzamanlı bölümü olarak adlandırılır async yöntem) çağrı iş parçacığı üzerinde çalışır. Cevabımda, iş parçacığı UI iş parçacığı olurdu ve Task.Run Bir iş parçacığı havuzu iş parçacığı kullanır. Hiç beklemediğim bir durumda ya da beklenen görev eşzamanlı olarak tamamlandığında cevabım yalnızca UI iş parçacığı kullanırdı. - i3arnon
Task.Run başlangıçtan itibaren bir iplik havuzu ipliği kullanıyor. Bu bazı durumlarda istenebilir, ancak istediğiniz tek şey SC'yi kaldırmaksa gereksizdir. - i3arnon
Tamam bu mantıklı. Benim durumumda bir eşzamanlı arayan ile ayrı bir iş parçacığı olmak için gerek yok, ve ben "eski moda" yapmak istediğim bir eşzamanlı arayıcıda bir örnek bulsam bile, bir iş parçacığı başlatmak, işlemeye devam, iş parçacığı için bekleyin , Eğer asyncyöntem düzgün yazılmış, ilk await yöntemde ilk optimize bekleme noktasında olacak, bu yüzden her şeyi başlangıçtan ayrı bir iş parçacığı üzerinde zamanlamada hiçbir nokta yoktur. Yani her iki durumda da, senin metodun kazanır Task.Run() - MikeJansen
Bunu yapmak için çok iyi bir sebep olabilir: ActonResult.Execute. Bunu yapmanın bir yolu yok. asynchenüz istek bağlamında. Bu mükemmel çalıştı. - John Gietzen


Bu kod, soruda vurguladığınız nedenler için tam olarak kilitlenmeyecektir - kod her zaman senkronizasyon bağlamı olmadan çalışır (iş parçacığı havuzu kullandığından beri) ve Wait sadece / kadar iş parçacığı döndürür iş parçacığı engeller.


2
2018-02-03 19:24





Zaman uyumsuz olarak senkronize yöntemini çağırmam gerektiğinde ve iş parçacığı UI iş parçacığı olduğunda, kilitlenme durumundan kaçınmamın bir yolu:

    public static T GetResultSafe<T>(this Task<T> task)
    {
        if (SynchronizationContext.Current == null)
            return task.Result;

        if (task.IsCompleted)
            return task.Result;

        var tcs = new TaskCompletionSource<T>();
        task.ContinueWith(t =>
        {
            var ex = t.Exception;
            if (ex != null)
                tcs.SetException(ex);
            else
                tcs.SetResult(t.Result);
        }, TaskScheduler.Default);

        return tcs.Task.Result;
    }

2
2017-12-02 08:35





Eğer kesinlikle şart senkronize olmayan bir yöntemden async yöntemini çağırın, kullandığınızdan emin olun ConfigureAwait(false) Eşzamansız yönteminizin içinde senkronizasyon içeriğinin yakalanmasını önlemek için çağrılır.

Bu tutmalı ama en iyi titriyor. Refactoring düşünmek için tavsiye ederim.  yerine.


1
2018-02-03 18:21



Daha fazla bilgi vermek için sorumu güncelledim. - MikeJansen
Bunun, "Eşzamansız bir yöntemden async yöntemini çağırmanız gerekiyorsa" olması gerektiği varsayılıyor ve "async yöntemi" bireşzamanlı bir "? - Brent Rittenhouse
@BrentRittenhouse Yup. - Yuval Itzchakov


Küçük özel senkronizasyon bağlamında, senkronizasyon fonksiyonu, kilitlenme oluşturmadan, uyumsuz fonksiyonun tamamlanmasını bekleyebilir. Orijinal iş parçacığı korunur, bu nedenle eşitleme yöntemi, uyumsuzluk işlevine çağrıdan önce ve sonra aynı iş parçacığını kullanır. İşte WinForms uygulaması için küçük bir örnek.

Imports System.Threading
Imports System.Runtime.CompilerServices

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        SyncMethod()
    End Sub

    ' waiting inside Sync method for finishing async method
    Public Sub SyncMethod()
        Dim sc As New SC
        sc.WaitForTask(AsyncMethod())
        sc.Release()
    End Sub

    Public Async Function AsyncMethod() As Task(Of Boolean)
        Await Task.Delay(1000)
        Return True
    End Function

End Class

Public Class SC
    Inherits SynchronizationContext

    Dim OldContext As SynchronizationContext
    Dim ContextThread As Thread

    Sub New()
        OldContext = SynchronizationContext.Current
        ContextThread = Thread.CurrentThread
        SynchronizationContext.SetSynchronizationContext(Me)
    End Sub

    Dim DataAcquired As New Object
    Dim WorkWaitingCount As Long = 0
    Dim ExtProc As SendOrPostCallback
    Dim ExtProcArg As Object

    <MethodImpl(MethodImplOptions.Synchronized)>
    Public Overrides Sub Post(d As SendOrPostCallback, state As Object)
        Interlocked.Increment(WorkWaitingCount)
        Monitor.Enter(DataAcquired)
        ExtProc = d
        ExtProcArg = state
        AwakeThread()
        Monitor.Wait(DataAcquired)
        Monitor.Exit(DataAcquired)
    End Sub

    Dim ThreadSleep As Long = 0

    Private Sub AwakeThread()
        If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume()
    End Sub

    Public Sub WaitForTask(Tsk As Task)
        Dim aw = Tsk.GetAwaiter

        If aw.IsCompleted Then Exit Sub

        While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False
            If Interlocked.Read(WorkWaitingCount) = 0 Then
                Interlocked.Increment(ThreadSleep)
                ContextThread.Suspend()
                Interlocked.Decrement(ThreadSleep)
            Else
                Interlocked.Decrement(WorkWaitingCount)
                Monitor.Enter(DataAcquired)
                Dim Proc = ExtProc
                Dim ProcArg = ExtProcArg
                Monitor.Pulse(DataAcquired)
                Monitor.Exit(DataAcquired)
                Proc(ProcArg)
            End If
        End While

    End Sub

     Public Sub Release()
         SynchronizationContext.SetSynchronizationContext(OldContext)
     End Sub

End Class

0
2017-09-27 13:56