且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

使用Task.Run()的同步方法,以避免死锁等待异步方法?

更新时间:2022-10-16 07:50:55

看来你了解涉及你的问题的风险,所以我会跳过演讲。

要回答你的实际的问题:是的,你可以使用 Task.Run 卸载的工作提高到一个线程池线程,不具有的SynchronizationContext 键,所以没有真正的风险了僵局。

然而,用另一个线程只是因为它没有SC是有点一个黑客,可能是昂贵的,因为安排的工作要在完成的线程池有代价的。

有一个更好的,更清晰的解决方案国际海事组织是简单地删除SC暂时使用 SynchronizationContext.SetSynchronizationContext 事后还原它。这很容易被封装成的IDisposable 这样你就可以在使用范围内使用它:

 公共类NoSynchronizationContextScope:IDisposable的
{
    私人只读的SynchronizationContext _synchronizationContext;
    公共NoSynchronizationContextScope()
    {
        _synchronizationContext = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(空);
    }
    公共无效的Dispose()
    {
        SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
    }
}
 

用法:

 私人无效MySynchronousMethodLikeDisposeForExample()
{
    使用(新NoSynchronizationContextScope())
    {
        MyAsyncMethod()等待()。
    }
}
 

UPDATE The purpose of this question is to get a simple answer about Task.Run() and deadlocking. I very much understand the theoretical reasoning for not mixing async and sync, and I take them to heart. I'm not above learning new things from others; I seek to do that whenever I can. There's just times when all a guy needs is a technical answer...

I have a Dispose() method that needs to call an async method. Since 95% of my code is async, refactoring isn't the best choice. Having an IAsyncDisposable (among other features) that's supported by the framework would be ideal, but we're not there yet. So in the mean time, I need to find a reliable way to call async methods from a synchronous method without deadlocking.

I'd prefer not to use ConfigureAwait(false) because that leaves the responsibility scattered all throughout my code for the callee to behave a certain way just in case the caller is synchronous. I'd prefer to do something in the synchronous method since it's the deviant bugger.

After reading Stephen Cleary's comment in another question that Task.Run() always schedules on the thread pool even async methods, it made me think.

In .NET 4.5 in ASP.NET or any other synchronization context that schedules tasks to the current thread / same thread, if I have an asynchronous method:

private async Task MyAsyncMethod()
{
    ...
}

And I want to call it from a synchronous method, can I just use Task.Run() with Wait() to avoid deadlocks since it queues the async method the the thread pool?

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

It seems you understand the risks involved in your question so I'll skip the lecture.

To answer your actual question: Yes, you can just use Task.Run to offload that work to a ThreadPool thread which doesn't have a SynchronizationContext and so there's no real risk for a deadlock.

However, using another thread just because it has no SC is somewhat of a hack and could be an expensive one since scheduling that work to be done on the ThreadPool has its costs.

A better and clearer solution IMO would be to simply remove the SC for the time being using SynchronizationContext.SetSynchronizationContext and restoring it afterwards. This can easily be encapsulated into an IDisposable so you can use it in a using scope:

public class NoSynchronizationContextScope : IDisposable
{
    private readonly SynchronizationContext _synchronizationContext;
    public NoSynchronizationContextScope()
    {
        _synchronizationContext = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(null);
    }
    public void Dispose()
    {
        SynchronizationContext.SetSynchronizationContext(_synchronizationContext);
    }
}

Usage:

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