将异步调用转为同步

时间:2012-04-12 13:25:32

标签: c# .net design-patterns .net-4.0 asynchronous

将异步调用转换为同步是否有任何良好的实践(模式)?
我有一个第三方库,其方法都是异步的,以获得任何方法的结果,你必须听一个事件,这将带来一些上下文。 基本上它看起来像:

service.BeginSomething(...);
service.OnBeginSomethingCompleted += ;

我需要的是在BeginSomething完成后执行一些代码(因此在触发OnBeginSomethingCompleted之后)。在事件中处理响应是非常不方便的。

我能想到的唯一方法是运行一个Thread.Sleep循环,等待表单上的某个字段更新,但它看起来不是很优雅的解决方案。

我正在使用.net 4.0。

7 个答案:

答案 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)

正如其他人所说,如果可能你应该尝试使自己的代码异步。如果这不起作用,您的第三方库是否支持标准BeginXXXEndXXX异步模式?如果是这样,那么使用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();
}