Utilisez Task.Run () dans une méthode synchrone pour éviter un blocage en attente d’une méthode asynchrone?

MISE À JOUR Le but de cette question est d’obtenir une réponse simple à propos de Task.Run() et de l’interblocage. Je comprends très bien le raisonnement théorique pour ne pas mélanger async et sync, et je les prends à cœur. Je ne suis pas au-dessus d’apprendre de nouvelles choses des autres; Je cherche à le faire chaque fois que je peux. Il y a juste des moments où tout ce qu’un gars a besoin est une réponse technique …

J’ai une méthode Dispose() qui doit appeler une méthode asynchrone. Étant donné que 95% de mon code est asynchrone, le refactoring n’est pas le meilleur choix. Avoir un IAsyncDisposable (entre autres fonctionnalités) pris en charge par le cadre serait idéal, mais nous n’y sums pas encore. Par conséquent, il me faut trouver un moyen fiable d’appeler des méthodes asynchrones à partir d’une méthode synchrone sans blocage.

Je préférerais ne pas utiliser ConfigureAwait(false) car cela laisse la responsabilité dispersée tout au long de mon code pour que l’appelé se comporte d’une certaine manière au cas où l’appelant serait synchrone. Je préférerais faire quelque chose avec la méthode synchrone car c’est le bougre déviant.

Après avoir lu le commentaire de Stephen Cleary dans une autre question que Task.Run() planifie toujours sur le pool de threads, même les méthodes asynchrones, cela m’a fait réfléchir.

Dans .NET 4.5 dans ASP.NET ou tout autre contexte de synchronisation qui planifie des tâches sur le thread actuel / le même thread, si j’ai une méthode asynchrone:

 private async Task MyAsyncMethod() { ... } 

Et je veux l’appeler à partir d’une méthode synchrone, puis-je simplement utiliser Task.Run() avec Wait() pour éviter les blocages, car elle met en queue la méthode async du pool de threads?

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

Il semble que vous compreniez les risques inhérents à votre question, je vais donc sauter la conférence.

Pour répondre à votre question: Oui, vous pouvez simplement utiliser Task.Run pour décharger ce travail vers un thread ThreadPool qui ne possède pas de SynchronizationContext , ce qui ThreadPool risque réel de blocage.

Cependant , utiliser un autre thread simplement parce qu’il n’a pas de SC est un peu un hack et peut être coûteux car la planification du travail à effectuer sur le ThreadPool a un coût.

Une solution plus efficace et plus claire serait simplement de supprimer le SC pour le moment à l’aide de SynchronizationContext.SetSynchronizationContext et de le restaurer ultérieurement. Cela peut facilement être encapsulé dans un IDisposable afin que vous puissiez l’utiliser dans une scope d’ using :

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

Usage:

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

Ce code ne sera pas bloqué pour les raisons que vous avez soulignées dans la question: le code s’exécute toujours sans contexte de synchronisation (depuis l’utilisation du pool de threads) et Wait bloque simplement le thread jusqu’à ce que la méthode revienne.

C’est ma façon d’éviter un blocage lorsque je dois appeler une méthode asynchrone de manière synchrone et que le thread peut être un thread d’interface utilisateur:

  public static T GetResultSafe(this Task task) { if (SynchronizationContext.Current == null) return task.Result; if (task.IsCompleted) return task.Result; var tcs = new TaskCompletionSource(); task.ContinueWith(t => { var ex = t.Exception; if (ex != null) tcs.SetException(ex); else tcs.SetResult(t.Result); }, TaskScheduler.Default); return tcs.Task.Result; } 

Si vous devez absolument appeler la méthode asynchrone à partir d’une méthode synchrone, veillez à utiliser ConfigureAwait(false) dans vos appels de méthode asynchrone pour éviter la capture du contexte de synchronisation.

Cela devrait tenir mais est au mieux fragile. Je conseillerais de penser à la refactorisation. au lieu.

Avec un petit contexte de synchronisation personnalisé, la fonction de synchronisation peut attendre l’achèvement de la fonction async, sans créer d’interblocage. Le thread d’origine étant préservé, la méthode sync utilise le même thread avant et après l’appel de la fonction async. Voici un petit exemple d’application WinForms.

 Imports System.Threading Imports System.Runtime.ComstackrServices 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  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