Should I expose synchronous wrappers for asynchronous methods?
In a previous post Should I expose asynchronous wrappers for synchronous methods?, I discussed “async over sync,” the notion of using synchronous functionality asynchronously and the benefits that doing so may or may not yield. The other direction of “sync over async” is also interesting to explore.
Avoid Exposing Synchronous Wrappers for Asynchronous Implementations
In my discussion of “async over sync,” I strongly suggested that if you have an API which internally is implemented synchronously, you should not expose an asynchronous counterpart that simply wraps the synchronous method in Task.Run. Rather, if a consumer of your library wants to use the synchronous method asynchronously, they can do so on their own. The post outlines a variety of benefits to this approach.
Similar guidance applies in the reverse direction as well: if you expose an asynchronous endpoint from your library, avoid exposing a synchronous method that just wraps the asynchronous implementation. Doing so hides from the consumer the true nature of the implementation, and it should be left up to the consumer how they want to consume the implementation. If the consumer chooses to block waiting for the asynchronous implementation to complete, that’s up to the caller, and they can do so with their eyes wide open.
This is even more important for the “sync over async” case than it is for the “async over sync” case, because for “sync overasync,” it can lead to significant problems with the application, such as hangs.
Basic Wrapping
To understand why, let’s examine what it looks like to wrap an asynchronous method with a synchronous one. How this is done, of course, depends on the asynchronous pattern employed by the method.
If I have an APM implementation:
public IAsyncResult BeginFoo(…, AsyncCallback callback, object state);
public TResult EndFoo(IAsyncResult asyncResult);
then a simple synchronous wrapper might look like:
public TResult Foo(…)
{
IAsyncResult ar = BeginFoo(…, null, null);
return EndFoo(ar);
}
In the APM pattern, if the EndXx method is provided with an IAsyncResult from the BeginXx method, and the IAsyncResult’s IsCompleted returns false, the call to EndXx needs to block until the operation is completed. Hence, we call BeginXx and then immediately call EndXx to block until the asynchronous operation has completed, at which point EndXx will be able to properly return the result of the operation or propagate its exception.
A more modern asynchronous implementation would use the Task-based Asynchronous Pattern:
public Task<TResult> FooAsync(…);
and for it a simple synchronous wrapper might look like:
public TResult Foo(…)
{
return FooAsync(…).Result;
}
Here we’re accessing the return Task’s Result, which will wait for the result to be available before returning it.
Real-World Example
These seem like reasonable wrappers, and depending on the situation, they may be… but of course they may not be.
Let’s say for sake of example that BeginFoo/EndFoo and FooAsync are all good asynchronous implementations, using async I/O under the covers such that no threading resources are consumed for the vast majority of their execution; only at the very end of their execution do they need to do a small amount of processing in order to handle the results received from the asynchronous I/O, and they’ll do this processing internally by queuing the work to the ThreadPool. This is quite reasonable, and a very common phenomenon in asynchronous implementations. And, let’s say that someone has set an upper limit on the number of threads in the ThreadPool to 25 using ThreadPool.SetMaxThreads or via a configuration flag.
Now, you call the synchronous Foo wrapper method from a ThreadPool thread, e.g. Task.Run(()=>Foo()). What happens? Foo is invoked, it kicks off the asynchronous operation, and then immediately blocks the ThreadPool thread waiting for the operation to complete. When the async I/O eventually completes, it queues a work item to the ThreadPool in order to complete the processing, and that work item will be handled by one of the 24 free threads (1 of the threads is currently blocked in Foo). Great.
But now let’s say that instead of queuing one task to call Foo, you queue 25 tasks to call Foo. Each of the 25 threads in the ThreadPool will pick up a task, and invoke Foo. Each of those calls will start some asynchronous I/O and will block until the work completes. The async I/O for all of the 25 Foo calls will complete and result in the final processing work items getting queued to the pool. But the pool threads are now all blocked waiting for the calls to Foo to complete, which won’t happen until the queued work items get processed, which won’t happen until threads become available, which won’t happen until the calls to Foo complete. Deadlock!
If this seems far-fetched, know that this exact situation was quite common in .NET 1.x with a popular Framework method: HttpWebRequest.GetResponse. GetResponse was implemented almost exactly like Foo is in our example wrapper: it called the asynchronous BeginGetResponse, and then turned around and immediately waited on it using EndGetResponse. Further, in .NET 1.x, the maximum number of threads in the ThreadPool was, by default, a low number like 25. As a result, developers using HttpWebRequest.GetResponse would find themselves in deadlock territory; as a stop-gap measure, the implementation had a heuristic to see how many threads were available in the ThreadPool, and would throw an exception if it appeared a deadlock would result (http://support.microsoft.com/kb/815637). In .NET 2.0, HttpWebRequest.GetResponse was fixed to be a truly synchronous implementation, rather than wrapping BeginGetResponse/EndGetResponse.
UI
The previous example may seem esoteric, but there is a scheduler in most apps today that has an extremely limited number of threads and that can easily be deadlocked with a situation like this: the UI. The blog post Await, and UI, and deadlocks! Oh my! describes how you can deadlock the UI thread in a similar situation.
Imagine the following:
private void button1_Click(object sender, EventArgs e)
{
Delay(15);
}
private void Delay(int milliseconds)
{
DelayAsync(milliseconds).Wait();
}
private async Task DelayAsync(int milliseconds)
{
await Task.Delay(milliseconds);
}
While this may look innocent, invoking Delay from the UI thread like this is almost certainly going to deadlock. By default, await’ing a Task will post the remainder of the method’s invocation back to the SynchronizationContext from which the await was issued (even if that “remainder” is just the completing of the method). Here, the UI thread calls Delay, which calls DelayAsync, which awaits a Task that won’t complete for a few milliseconds. The UI thread then synchronously blocks in the call to Wait(), waiting for the Task returned from DelayAsync to complete. A few milliseconds later, the task returned from Task.Delay completes, and the await causes the remainder of DelayAsync’s execution to be posted back to the UI’s SynchronizationContext. That continuation needs to execute so that the Task returned from DelayAsync may complete, but that continuation can’t execute until the call to button1_Click returns, which won’t happen until the Task returned from DelayAsync completes, which won’t happen until the continuation executes. Deadlock!
Had Delay instead been invoked from a console app, or from a unit test harness that didn’t have a similarly constrictive SynchronizationContext, everything likely would have completed successfully. This highlights the danger in such sync over async behavior: its success can be significantly impacted by the environment in which it’s used. That’s a core reason why it should be left up to the caller to decide whether to do such blocking, as they are much more aware of the environment in which they’re operating than is the library they’re calling.
Async All the Way Down
The point here is that you need to be extremely careful when wrapping asynchronous APIs as synchronous ones, as if you’re not careful, you could run into real problems. If you ever find yourself thinking you need to call an asynchronous method from something that’s expecting a synchronous invocation (e.g. you’re implementing an interface which has a synchronous method on it, but in order to implement that interface you need to use functionality that’s only exposed asynchronously), first make sure it’s truly, truly necessary; while it may seem more expedient to wrap “sync over async” rather than to re-plumb this or that code path to be asynchronous from top to bottom, the refactoring is often the better long-term solution if it’s possible.
What if I really do need “sync over async”?
In some cases, for whatever reason, you may actually need to do “sync over async.” In such cases, there are some things you can do to ease the pain.
Test in Multiple Environments
Make sure you test your wrapper in a variety of environments: from a UI thread, from the ThreadPool, under stress on the ThreadPool with a low maximum set on the number of allowed threads, etc.
Avoid Unnecessary Marshaling
If at all possible, make sure the async implementation you’re calling doesn’t need the blocked thread in order to complete the operation (that way, you can just use normal blocking mechanisms to wait synchronously for the asynchronous work to complete elsewhere). In the case of async/await, this typically means making sure that any awaits inside of the asynchronous implementation you’re calling are using ConfigureAwait(false) on all await points; this will prevent the await from trying to marshal back to the current SynchronizationContext. As a library implementer, it’s a best practice to always use ConfigureAwait(false) on all of your awaits, unless you have a specific reason not to; this is good not only to help avoid these kinds of deadlock problems, but also for performance, as it avoids unnecessary marshaling costs.
Offload to Another Thread
Consider offloading to a different thread, which is typically possible unless the method you’re invoking has some kind of thread affinity (e.g. it accesses UI controls). Let’s say you have methods like the following:
int Sync() // caller needs this to return synchronously
{
return Library.FooAsync().Result;
}
// in a library; uses await without ConfigureAwait(false)
public static Task<int> FooAsync();
As described above, FooAsync is using await without a ConfigureAwait(false), and as you don’t own the code, you’re unable to fix that. Further, the Sync method you’re implementing is being called from the UI thread, or more generally from a context prone to deadlocking due to a limited number of participating threads (in the case of the UI, that limited number is one). Solution? Ensure that the await in the FooAsync method doesn’t find a context to marshal back to. The simplest way to do that is to invoke the asynchronous work from the ThreadPool, such as by wrapping the invocation in a Task.Run, e.g.
int Sync()
{
return Task.Run(() => Library.FooAsync()).Result;
}
FooAsync will now be invoked on the ThreadPool, where there won’t be a SynchronizationContext, and the continuations used inside of FooAsync won’t be forced back to the thread that’s invoking Sync().
Consider a Nested Message Loop
If you do find that you need to invoke an asynchronous method and “block” waiting for it (to satisfy a synchronous contract), and if that method marshals back to the current thread, and if changing the method’s implementation won’t work (e.g. because you don’t have the ability to modify it), and if offloading won’t work (e.g. because the asynchronous method is thread affine), you’re in a tight spot. One saving grace may be that there are multiple ways “blocking” behavior can be achieved.
The simplest and most general approach to blocking is to just to wait on a synchronization primitive. This is typically what happens when you call an EndXx method on the IAsyncResult returned from the BeginXx method, with the EndXx method using the IAsyncResult’s AsyncWaitHandle to wait until the IAsyncResult transitions to a completed state. The primitive’s waiting implementation may itself have some smarts that could help in some limited situations. For example, if you Wait on a Task that is backed by a delegate (e.g. it was created with Task.Run, Task.Factory.StartNew, etc.) and that’s waiting to run, it’s possible that TPL will decide to execute the Task on the current thread as part of the Wait call, a behavior we often describe as inlining. Typically, however, such specialized behaviors only apply in corner cases.
If you’re blocking the UI thread, it’s likely that the UI’s message loop won’t be pumping all the messages necessary to keep the UI responsive and to eventually process the message that would allow the thing you’re waiting on to complete and to avoid the deadlock. For such cases, as a last ditch effort, you could consider “blocking” via a “nested message loop”.
A nested message loop is just what it sounds like. If, for example, you’re executing as part of a button click event handler, you’re being called from a message loop that’s dispatching the processing for a button click message it received. If that button click handler then itself synchronously spins up its own message loop, that inner loop is nested within the outer one. Here, for example, is a nested message loop in Windows Forms that spins waiting for a task to complete; the UI will remain responsive, since we’re forcing messages in the queue to be drained via the repeated calls to Application.DoEvents (warning: this is just for demonstrative purposes, and I’m not recommending you do this… see “Keeping your UI Responsive and the Dangers of Application.DoEvents” for more details):
static T WaitWithNestedMessageLoop<T>(this Task<T> task)
{
while (!task.IsCompleted)
Application.DoEvents();
return task.Result;
}
Some UI frameworks have built-in support for nested message loops, and in a more efficient and robust manner than the ad-hoc spinning with DoEvents I’ve done above. WPF, for example, uses the DispatchFrame and Dispatcher.PushFrame constructs to process a nested message loop. In the following example, the nested loop will exit when the task completes and sets the DispatcherFrame’s Continue property to false.
static T WaitWithNestedMessageLoop<T>(this Task<T> task)
{
var nested = new DispatcherFrame();
task.ContinueWith(_ => nested.Continue = false, TaskScheduler.Default);
Dispatcher.PushFrame(nested);
return task.Result;
}
Even when a framework has a built-in notion of nested message loops, they’re far from an ideal solution. It’s much better to asynchronously wait whenever possible, allowing the higher-level message loop to handle all of the processing. This is just something you can consider if you’re truly in a bind.
Conclusion
Getting back to the original question for this blog post: should you expose synchronous wrappers for asynchronous methods? Please try hard not to; leave it up to the consumer of the your method to do so if they must. You should consider the possibility that someone will consume your functionality synchronously, so code accordingly, with as few dependences on the threading environment as possible. And at the very least, if you must expose a synchronous wrapper that will just block waiting for an asynchronous implementation to complete, please document it accordingly so that a consumer knows what they’re consuming and can plan accordingly. If you must consume an asynchronous method in a synchronous manner, do so with your eyes wide open, being careful to think through potentially problematic situations like deadlocks.
Should I expose synchronous wrappers for asynchronous methods?的更多相关文章
- Atitit 五种IO模型attilax总结 blocking和non-blocking synchronous IO和asynchronous I
Atitit 五种IO模型attilax总结 blocking和non-blocking synchronous IO和asynchronous I 1.1. .3 进程的阻塞1 1.2. 网络 ...
- 论文笔记之:Asynchronous Methods for Deep Reinforcement Learning
Asynchronous Methods for Deep Reinforcement Learning ICML 2016 深度强化学习最近被人发现貌似不太稳定,有人提出很多改善的方法,这些方法有很 ...
- What is the difference between concurrency, parallelism and asynchronous methods?
Ref: http://stackoverflow.com/questions/4844637/what-is-the-difference-between-concurrency-paralleli ...
- Asynchronous Methods for Deep Reinforcement Learning(A3C)
Mnih, Volodymyr, et al. "Asynchronous methods for deep reinforcement learning." Internatio ...
- 同步(Synchronous)和异步(Asynchronous)方法的区别
同步(Synchronous)和异步(Asynchronous)方法的区别 在讲之前,我们先来看<Computer Organization>中对于同步和异步的一个例子: 同步读写和异步读 ...
- 同步IO与一部IO、IO多路复用(番外篇)select、poll、epoll三者的区别;blocking和non-blocking的区别 synchronous IO和asynchronous IO的区别
Python之路,Day9 , IO多路复用(番外篇) 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. ...
- synchronous interrupt and asynchronous interrupt
Asynchronous interrupt 中斷请求信号来自CM3内核的外面,来自各种片上外设和外扩的外设,对CM3来说是"异步"的: e.g. usb otg device S ...
- Asynchronous Methods for Deep Reinforcement Learning
郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! ICML 2016 Abstract 我们提出了一个概念上简单且轻量级的深度强化学习框架,该框架使用异步梯度下降来优化深度神经网络控制器. ...
- 同步(Synchronous)和异步(Asynchronous)
目录 概念性 图示例 举个例子 回到顶部 概念性 同步和异步通常用来形容一次方法调用. 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为. 异步方法调用更像一个消息传递,一旦开始 ...
随机推荐
- Android 性能优化总结
App 流畅运行,是用户体验的第一步.App 流程运行,涉及到性能优化,主要涉及到布局优化, 绘制优化,内存泄漏优化,响应速度优化,列表展示优化,Bitmap 优化,线程优化,包大小优化. 布局优化 ...
- asp.net 中用easyui中的treegird的简单使用
几乎每个‘数人头’项目中都会用到的功能,这里先记下来,以后直接到这里复制代码就行了,ASP.NET MVC中的使用 数据库用户表中的除了有个parentid父级ID外,我还多加了以个字段,parent ...
- ubuntu java 环境配置
下载oracle java sudo add-apt-repository ppa:webupd8team/java -y sudo apt-get update sudo apt-get insta ...
- Python 的并发编程
这篇文章将讲解 Python 并发编程的基本操作.并发和并行是对孪生兄弟,概念经常混淆.并发是指能够多任务处理,并行则是是能够同时多任务处理.Erlang 之父 Joe Armstrong 有一张非常 ...
- Java 的 I/O 类库的基本架构
Java 的 I/O 类库的基本架构 I/O 问题是任何编程语言都无法回避的问题,可以说 I/O 问题是整个人机交互的核心问题,因为 I/O 是机器获取和交换信息的主要渠道.在当今这个数据大爆炸时代, ...
- 利用es-checker检测当前node对ES6的支持情况
ode.js发展非常快,对es6特性的支持也越来越良心,但node.js版本很多,各版本对es6的支持度都不一样,为了能清晰的了解各版本对es6特性的支持,需要有一个工具能提供比较清晰的支持说明,甚至 ...
- Linux之Kill进程的N种方法
常规篇: 首先,用ps查看进程,方法如下: $ ps -ef …… smx 1822 1 0 11:38 ? 00:00:49 gnome-terminal smx ...
- 4.3之后的PingPong效果实现
旧版本的Unity提供Animation编辑器来编辑物理动画. 在其下方可以设置动画是Loop或者是Pingpong等运动效果. 但是,在4.3之后,Unity的动画系统发生了较大的变化. 相信很多童 ...
- 【Delphi】基于状态机的串口通信
通信协议 串行通信接口(如RS232.RS485等)作为计算机与单片机交互数据的主要接口,广泛用于各类仪器仪表.工业监测及自动控制领域中. 通信协议是需要通信的双方所达成的一种约定,它对包括数据格式. ...
- Java获取 ISO 8601格式时间
https://blog.csdn.net/fang323619/article/details/74909587 ****************************************** ...