Eşzamansız bir zaman uyumsuz işlem bekliyor ve neden bekle () programı burada donduruyor


318

Önsöz : Sadece bir çözüm değil, bir açıklama arıyorum. Çözümü zaten biliyorum.

Görev tabanlı Eşzamansız Kalıp (TAP), zaman uyumsuz ve beklemedeki MSDN makalelerini inceleyerek birkaç gün geçirmeme rağmen, hala bazı ince detaylar hakkında biraz kafam karıştı.

Windows Mağazası Uygulamaları için bir kaydedici yazıyorum ve hem eşzamansız hem de eşzamanlı günlük kaydını desteklemek istiyorum. Eşzamansız yöntemler TAP'ı takip eder, eşzamanlı olanlar bunları gizlemeli ve sıradan yöntemler gibi görünmeli ve çalışmalıdır.

Bu, asenkron günlüklemenin temel yöntemidir:

private async Task WriteToLogAsync(string text)
{
    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync("log.log",
        CreationCollisionOption.OpenIfExists);
    await FileIO.AppendTextAsync(file, text,
        Windows.Storage.Streams.UnicodeEncoding.Utf8);
}

Şimdi ilgili senkronize yöntem ...

Sürüm 1 :

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Wait();
}

Bu doğru görünüyor, ancak çalışmıyor. Tüm program sonsuza dek donuyor.

Sürüm 2 :

Hmm .. Belki görev başlatılmadı?

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Start();
    task.Wait();
}

Bu atar InvalidOperationException: Start may not be called on a promise-style task.

Sürüm 3:

Hmm .. Task.RunSynchronouslykulağa umut verici geliyor.

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.RunSynchronously();
}

Bu atar InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

Sürüm 4 (çözüm):

private void WriteToLog(string text)
{
    var task = Task.Run(async () => { await WriteToLogAsync(text); });
    task.Wait();
}

Bu çalışıyor. Yani, 2 ve 3 yanlış araçlardır. Peki 1? 1 ile ilgili sorun nedir ve 4 ile farkı nedir? 1'in donmasına neden olan nedir? Görev nesnesiyle ilgili bir sorun var mı? Açık olmayan bir kilitlenme var mı?


Başka bir yerde açıklama yapma şansın var mı? Aşağıdaki cevaplar gerçekten fikir vermiyor. Aslında .net 4.0 değil 4.5 / 5 kullanıyorum, bu yüzden bazı işlemleri kullanamazsınız ama aynı sorunları çalıştıran.
amadib

3
@ amadib, sürüm 1 ve 4 [rpvided cevaplarda açıklanmıştır. Ver.2 ve 3, yeniden başlatmayı deneyin zaten başlatılan görev. Sorunuzu gönderin. .NET 4.0'da .NET 4.5 async / await sorunlarına nasıl sahip olabileceğiniz belirsizdir
Gennady Vanin Геннадий Ванин

1
Sürüm 4, Xamarin Forms için en iyi seçenektir. Seçeneklerin geri kalanını denedik ve her durumda işe yaramadık ve çıkmaza
Ramakrishna

Teşekkürler! Sürüm 4 benim için çalıştı. Ama hala eşzamansız olarak çalışıyor mu? Ben async anahtar kelime var çünkü ben varsayıyorum.
sshirley

Yanıtlar:


189

awaitSenin asenkron yöntem içinde UI iş parçacığı geri gelmek için çalışıyor.

UI iş parçacığı tüm görevin tamamlanmasını beklemekle meşgul olduğundan, bir kilitlenmeniz var.

Task.Run()Sorunu çözmek için zaman uyumsuz çağrıyı taşımak .
Zaman uyumsuz çağrı artık bir iş parçacığı havuzu iş parçacığı üzerinde çalıştığı için, UI iş parçacığına geri gelmek için çalışmaz ve bu nedenle her şey çalışır.

Alternatif olarak, StartAsTask().ConfigureAwait(false)iç işlemi beklemeden önce, UI iş parçacığı yerine iş parçacığı havuzuna geri dönmesini ve kilitlenmeyi tamamen önleyerek arama yapabilirsiniz.


9
+1. İşte bir açıklama daha - Bekliyoruz, UI ve çıkmazlar! Aman!
Alexei Levenkov

13
ConfigureAwait(false)Bu durumda uygun bir çözümdür. Yakalanan bağlamda geri çağrıları çağırmaya gerek olmadığı için olmamalıdır. Bir API yöntemi olarak, tüm arayanları UI bağlamından çıkmaya zorlamak yerine dahili olarak ele almalıdır.
13'te

@Servy ConfigureAwait'ten bahsettiğinizden beri soruyorum. .Net3.5 kullanıyorum ve ben kullandığım zaman uyumsuz kütüphanede mevcut değil çünkü yapılandırmak için yapılandırmayı kaldırmak zorunda kaldı. Kendi yazımı nasıl yazabilirim veya eşzamansız çağrımı beklemenin başka bir yolu var mı? Çünkü yöntemim de takılıyor. Görevim yok ama Görev değil.Run. Bu büyük olasılıkla tek başına bir soru olabilir.
flexxxit

@flexxxit: Kullanmalısınız Microsoft.Bcl.Async.
SLaks

48

asyncEşzamanlı koddan arama kodu oldukça zor olabilir.

Blogumda bu kilitlenmenin tüm nedenlerini açıklıyorum . Kısacası, her birinin başında varsayılan olarak kaydedilen awaitve yöntemi sürdürmek için kullanılan bir "bağlam" vardır .

Bu bir UI bağlamında çağrılırsa, awaittamamlandığında, asyncyöntem yürütmeye devam etmek için bu bağlamı yeniden girmeye çalışır. Ne yazık ki, Wait(veya Result) kullanarak kod bu bağlamda bir iş parçacığını engeller, böylece asyncyöntem tamamlanamaz.

Bundan kaçınmak için yönergeler:

  1. ConfigureAwait(continueOnCapturedContext: false)Mümkün olduğunca kullanın . Bu, asyncyöntemlerinizin içeriğe yeniden girmek zorunda kalmadan yürütmeye devam etmesini sağlar .
  2. Sonuna kadar kullanın async. Veya awaityerine kullanın .ResultWait

Metodunuz doğal olarak asenkron ise, muhtemelen (muhtemelen) senkronize bir sarıcı açığa çıkarmamalısınız .


Bunu asyncnasıl yapacağımı desteklemeyen ve bir yangını önleyip durumu unutamayan bir catch () içinde bir Async görevi yürütmem gerekiyor .
Zapnologica

1
@Zapnologica: VS2015'ten itibaren bloklar halinde awaitdesteklenir catch. Eski bir sürümdeyseniz, istisnayı yerel bir değişkene atayabilir awaitve catch bloğundan sonra bunu yapabilirsiniz .
Stephen Cleary

5

İşte yaptığım şey

private void myEvent_Handler(object sender, SomeEvent e)
{
  // I dont know how many times this event will fire
  Task t = new Task(() =>
  {
    if (something == true) 
    {
        DoSomething(e);  
    }
  });
  t.RunSynchronously();
}

harika çalışıyor ve UI iş parçacığını engellemiyor


0

Küçük özel senkronizasyon içeriği ile senkronizasyon fonksiyonu, kilitlenme oluşturmadan senkronize olmayan fonksiyonun tamamlanmasını bekleyebilir. 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.