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