C# 在同步方法中使用Task.Run()以避免在异步方法上等待死锁?

C# 在同步方法中使用Task.Run()以避免在异步方法上等待死锁?,c#,.net,asynchronous,task-parallel-library,async-await,C#,.net,Asynchronous,Task Parallel Library,Async Await,更新此问题的目的是获得有关Task.Run()和死锁的简单答案。我非常理解不将异步和同步混合在一起的理论推理,我把它们放在心上。我并不高人一等地向别人学习新事物;我会尽我所能做到这一点。有时候一个男人需要的只是一个技术性的答案 我有一个Dispose()方法需要调用异步方法。由于我95%的代码是异步的,重构不是最佳选择。拥有一个由框架支持的IAsyncDisposable(以及其他功能)将是理想的,但我们还没有做到这一点。因此,同时,我需要找到一种可靠的方法,从同步方法调用异步方法,而不会出现死

更新此问题的目的是获得有关
Task.Run()
和死锁的简单答案。我非常理解不将异步和同步混合在一起的理论推理,我把它们放在心上。我并不高人一等地向别人学习新事物;我会尽我所能做到这一点。有时候一个男人需要的只是一个技术性的答案

我有一个
Dispose()
方法需要调用异步方法。由于我95%的代码是异步的,重构不是最佳选择。拥有一个由框架支持的
IAsyncDisposable
(以及其他功能)将是理想的,但我们还没有做到这一点。因此,同时,我需要找到一种可靠的方法,从同步方法调用异步方法,而不会出现死锁

我不希望使用
ConfigureAwait(false)
,因为这使得被调用方的责任分散在我的整个代码中,以确保调用方是同步的。我更喜欢用同步方法做一些事情,因为这是一个越轨的错误

在阅读了Stephen Cleary在另一个问题中的评论后,我开始思考
Task.Run()
总是在线程池上调度,甚至异步方法

在ASP.NET的.NET 4.5或将任务调度到当前线程/同一线程的任何其他同步上下文中,如果我有异步方法:

private async Task MyAsyncMethod()
{
    ...
}
我想从同步方法调用它,我可以只使用
Task.Run()
Wait()
来避免死锁,因为它将异步方法排入线程池的队列吗

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();
}
private void mysynchronousmethodlikedisposeExample()
{
//MyAsyncMethod将排队到线程池
//所以它不应该与Wait()死锁??
Task.Run((Func)MyAsyncMethod.Wait();
}
如果必须从同步方法调用异步方法,请确保在异步方法调用中使用
ConfigureAwait(false)
,以避免捕获同步上下文

这应该能保持,但充其量是不稳定的。我建议考虑重构。
相反。

由于您在问题中强调的原因,此代码不会死锁-代码总是在没有同步上下文的情况下运行(因为使用了线程池),而
Wait
将简单地阻止线程,直到/if方法返回。

您似乎理解问题中涉及的风险,因此我将跳过本课

回答您的实际问题:是的,您可以只使用
任务。运行
将该工作卸载到
线程池
线程,该线程没有
同步上下文
,因此没有发生死锁的真正风险

然而,仅仅因为没有SC而使用另一个线程是一种黑客行为,而且可能是一种昂贵的行为,因为在
线程池
上安排要完成的工作是有成本的

IMO的一个更好、更清晰的解决方案是暂时使用
SynchronizationContext.SetSynchronizationContext
删除SC,然后恢复它。这可以很容易地封装到
IDisposable
中,因此您可以使用
范围在
中使用它:

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);
    }
}
用法:

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

使用小型自定义同步上下文,同步函数可以等待异步函数完成,而不会造成死锁。原始线程被保留,所以sync方法在调用异步函数之前和之后使用相同的线程。下面是WinForms应用程序的一个小示例

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
导入系统线程
导入System.Runtime.CompilerServices
公开课表格1
私有子表单1_Load(发送方作为对象,e作为事件参数)处理MyBase.Load
同步方法()
端接头
'正在同步方法内等待完成异步方法
公共子同步方法()
Dim sc作为新sc
sc.WaitForTask(AsyncMethod())
sc.发布()
端接头
公共异步函数AsyncMethod()作为任务(布尔型)
等待任务。延迟(1000)
返回真值
端函数
末级
公开课理学士
继承SynchronizationContext
Dim OldContext作为SynchronizationContext
Dim ContextThread作为线程
次新
OldContext=SynchronizationContext.Current
ContextThread=Thread.CurrentThread
SynchronizationContext.SetSynchronizationContext(Me)
端接头
作为新对象获取的Dim数据
Dim WorkWaitingCount的长度=0
将ExtProc设置为SendOrPostCallback
Dim ExtProcArg作为对象
公共重写子帖子(d为SendOrPostCallback,状态为Object)
联锁。增量(工作等待计数)
Monitor.Enter(已获取数据)
ExtProc=d
ExtProcArg=状态
AwakeThread()
Monitor.Wait(已获取数据)
Monitor.Exit(已获取数据)
端接头
当长=0时变暗线程睡眠
私有子AwakeThread()
如果Interlocked.Read(ThreadSleep)>0,则ContextThread.Resume()
端接头
公共子WaitForTask(Tsk作为任务)
Dim aw=Tsk.GetAwaiter
如果aw完成,则退出Sub
联锁时。读取(WorkWaitingCount)>0或aw.IsCompleted=False
如果联锁读取(WorkWaitingCount)=0,则
联锁。增量(线程睡眠)
ContextThread.Suspend()
联锁。减量(线程休眠)
其他的
联锁。减量(工作等待计数)
Monitor.Enter(已获取数据)
Dim Proc=ExtProc
Dim ProcArg=ExtProcArg
监视器脉冲(数据采集)
Monitor.Exit(已获取数据)
Proc(ProcArg)
如果结束
结束时
端接头
公开分稿()
SynchronizationContext.SetSynchronizationContext(旧上下文)
端接头
末级

这是我在必须调用异步方法synchrono时避免死锁的方法
    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;
    }