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();
}
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();
}
}
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.
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;
}
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.
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