将异步调用转换为同步调用
本文关键字:调用 同步 转换 异步 | 更新日期: 2023-09-27 18:30:57
将异步调用转换为同步调用是否有任何良好的做法(模式)?
我有一个第三方库,它的方法都是异步的,要获得almoust的结果,你必须听一个事件,这将带来一些上下文。基本上它看起来像:
service.BeginSomething(...);
service.OnBeginSomethingCompleted += ;
我需要的是在 BeginSomething 之后执行一些代码,当它真正完成时(因此在 OnBeginSomethingCompleted 被触发之后)。在事件中处理响应是非常不方便的。
我能想到的唯一方法是运行 Thread.Sleep 循环并等待表单上的某个字段更新,但它看起来不像非常优雅的解决方案。
我正在使用.net 4.0。
您可以对主类进行子类化并提供操作的同步版本。如果子类化不是一个选项,你可以创建一个扩展方法。事情可能看起来是这样的。
public class Subclass : BaseClass
{
public void Something()
{
using (var complete = new ManualResetEventSlim(false))
{
EventHandler handler = (sender, args) => { complete.Set(); };
base.OnBeginSomethingCompleted += handler;
try
{
base.BeginSomething();
complete.Wait();
}
finally
{
base.OnBeginSomethingCompleted -= handler;
}
}
}
}
更新:
我应该指出的一件事是,在某些情况下这可能是有问题的。请考虑此示例。
var x = new Subclass();
x.BeginSomething();
x.Something();
很明显,Something
中的handler
可以从上一次调用 BeginSomething
接收 OnBeginSomethingCompleted
事件。确保以某种方式防止这种情况。
使用ManualResetEvent
.在同步包装器中创建它,然后将其作为状态对象的一部分传递给service.BeginSomething()
调用。通话后,立即WaitOne()
,这将阻止。
在 service.OnBeginSomethingCompleted
事件中,将其从状态对象中提取并设置,这将取消阻止同步调用方。
正如其他人所说,如果可能的话,你应该尝试使自己的代码异步。如果这不起作用,您的第三方库是否支持标准BeginXXX
,EndXXX
异步模式?如果是这样,那么使用 TPL 会让您轻松。您的代码将如下所示:
using System.Threading.Tasks;
...
var task = Task<TResult>.Factory.FromAsync(
service.BeginSomething, service.EndSomething, arg1, arg2, ..., null);
task.Wait();
var result = task.Result;
要使用的特定重载将取决于需要传递的参数数。您可以在此处查看列表。
如果BeginSomething()
返回一个IAsyncResult
(就像委托的.BeginInvoke
一样),你可以从中得到WaitHandle
:
service.OnBeginSomethingCompleted += ;
var asyncResult = service.BeginSomething();
asyncResult.AsyncWaitHandle.WaitOne(); // Blocks until process is complete
顺便说一下,通过在启动异步进程后分配事件处理程序,您将引入一个争用条件,在该条件中,异步调用可能会在事件注册之前完成,从而导致它永远不会触发。
你可能想看看反应式扩展
使用Rx,您基本上可以将其包装到"事件"中 - 通常使用一些 lambda 表达式来订阅someClass.SomeEvent.Subscribe(d=>...)
之类的操作来处理您需要的内容。还可以使用 ObserveOn
在 GUI 线程上处理它(请参阅详细信息,这只是一个提示)。
另一种选择是使用 async await
(现在可以与 VS 2010 一起使用)。
希望这有帮助
注意:Rx 对异步方法具有本机支持,只需一次调用即可将它们转换为 Rx 事件。看看Observable.FromAsyncPattern
FromAsyncPattern
现代软件开发(在Windows平台上也是如此)的总体趋势是异步运行。
实际上,根据Windows8软件设计指南,如果代码运行超过50ms,则必须是异步的。
所以我不建议阻止线程,而是从该库中受益,并为用户提供一些漂亮的动画,说"等待,响应来了",或者类似的东西,或者一些进度条。
简而言之,不要阻止线程,通知用户应用程序中正在发生的事情并使其保持异步。
这个解决方案类似于Brian Gideon的解决方案,但我认为对于您要做的事情来说更干净一些。 它使用 Monitor 对象使调用线程等待,直到触发 T已完成 事件。
public class SomeClass : BaseClass
{
public void ExecuteSomethingAndWaitTillDone()
{
// Set up the handler to signal when we're done
service.OnBeginSomethingCompleted += OnCompleted;
// Invoke the asynchronous method.
service.BeginSomething(...);
// Now wait until the event occurs
lock (_synchRoot)
{
// This waits until Monitor.Pulse is called
Monitor.Wait(_synchRoot);
}
}
// This handler is called when BeginSomething completes
private void OnCompleted(object source, ...)
{
// Signal to the original thread that it can continue
lock (_synchRoot)
{
// This lets execution continue on the original thread
Monitor.Pulse(_synchRoot);
}
}
private readonly Object _synchRoot = new Object();
}