将异步调用转换为同步是否有任何良好的实践(模式)?
我有一个第三方库,其方法都是异步的,以获得任何方法的结果,你必须听一个事件,这将带来一些上下文。
基本上它看起来像:
service.BeginSomething(...);
service.OnBeginSomethingCompleted += ;
我需要的是在BeginSomething完成后执行一些代码(因此在触发OnBeginSomethingCompleted之后)。在事件中处理响应是非常不方便的。
我能想到的唯一方法是运行一个Thread.Sleep循环,等待表单上的某个字段更新,但它看起来不是很优雅的解决方案。
我正在使用.net 4.0。
答案 0 :(得分:8)
您可以继承主类并提供操作的同步版本。如果子类化不是一个选项,您可以创建一个扩展方法。这是事情的样子。
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();
显而易见,handler
中的Something
可能会在之前的OnBeginSomethingCompleted
调用中收到BeginSomething
个事件。确保你以某种方式防范这一点。
答案 1 :(得分:2)
正如其他人所说,如果可能你应该尝试使自己的代码异步。如果这不起作用,您的第三方库是否支持标准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;
您想要使用的特定过载取决于您需要传递多少参数。您可以看到列表here。
答案 2 :(得分:2)
使用ManualResetEvent
。在同步包装器中创建它,然后将其作为状态对象的一部分传递给service.BeginSomething()
调用。电话会议结束后,WaitOne()
就会立即阻止。
在service.OnBeginSomethingCompleted
事件中从状态对象中提取并设置它,这将取消阻止同步调用者。
答案 3 :(得分:1)
您可能需要查看Reactive Extensions
使用Rx,您可以基本上将其包装成'事件' - 像someClass.SomeEvent.Subscribe(d=>...)
这样做,通常使用一些lambda表达式来处理您需要的内容。还可以使用ObserveOn
在GUI线程上处理它(请参阅详细信息,这只是一个提示)。
其他选项是使用async await
(现在可用于VS 2010)。
希望这会有所帮助
注意:Rx对异步方法有本机支持,只需一次调用即可将其转换为Rx事件。请查看Observable.FromAsyncPattern
FromAsyncPattern
答案 4 :(得分:1)
如果BeginSomething()
返回IAsyncResult
(就像代理人的.BeginInvoke
那样),您可以从中获取WaitHandle
:
service.OnBeginSomethingCompleted += ;
var asyncResult = service.BeginSomething();
asyncResult.AsyncWaitHandle.WaitOne(); // Blocks until process is complete
顺便说一句,通过在启动异步进程后分配事件处理程序,您将引入一个竞争条件,其中异步调用可能在事件注册之前完成,从而导致它永远不会触发。
答案 5 :(得分:0)
现代软件开发的一般趋势(在Windows平台上也是)运行,异步可能。
实际上,从Windows8软件设计指南来看,如果代码运行超过50毫秒,则必须是异步的。
因此我不建议阻止该线程,而是从该库中受益并向用户提供一些漂亮的动画,说“等待,响应即将发生”,或类似的东西,或者某些进度条。
简而言之,不要阻止线程,通知用户应用程序中发生的事情并保持异步。
答案 6 :(得分:0)
这个解决方案类似于Brian Gideon的解决方案,但我认为你想要做的事情有点清洁。它使用Monitor对象使调用线程等待,直到触发Completed事件。
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();
}