8

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

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

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

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

我正在使用.net 4.0。

4

7 回答 7

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

很明显,handlerinSomething可以接收OnBeginSomethingCompleted来自上一次调用的事件BeginSomething。确保以某种方式防范这种情况。

于 2012-04-12T13:40:34.577 回答
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;

您要使用的特定重载将取决于您需要传递的参数数量。您可以在此处查看列表。

于 2012-04-12T13:30:33.207 回答
2

使用ManualResetEvent. 在您的同步包装器中创建它,然后将其service.BeginSomething()作为状态对象的一部分传递给调用。通话后,立即WaitOne()就可以了,这将阻塞。

如果service.OnBeginSomethingCompleted从状态对象中提取它并设置它,这将解除同步调用者的阻塞。

于 2012-04-12T13:31:35.350 回答
1

如果BeginSomething()返回一个IAsyncResult(就像一个代表.BeginInvoke会做的那样),你可以从中得到WaitHandle

service.OnBeginSomethingCompleted += ;
var asyncResult = service.BeginSomething();
asyncResult.AsyncWaitHandle.WaitOne(); // Blocks until process is complete

顺便说一句,通过在启动异步进程后分配事件处理程序,您将引入一个竞争条件,其中异步调用可能在事件注册之前完成,导致它永远不会触发。

于 2012-04-12T13:34:15.897 回答
1

你可能想看看Reactive Extensions

使用 Rx,您基本上可以将其包装到一个“事件”中——someClass.SomeEvent.Subscribe(d=>...)通常使用一些 lambda 表达式来处理您需要的内容。也用于ObserveOn在 GUI 线程上处理它(请参阅详细信息,这只是一个提示)。

其他选项是使用async await(现在可用于 VS 2010)。

希望这可以帮助

注意:Rx 对异步方法具有原生支持,只需一次调用即可将它们转换为 Rx 事件。看看Observable.FromAsyncPattern FromAsyncPattern

于 2012-04-12T13:32:17.637 回答
0

现代软件开发的总趋势(也在 Windows 平台上)是运行,异步是可能的。

实际上,从 Windows8 软件设计指南来看,如果代码运行时间超过 50 毫秒,则它必须是异步的。

所以我不建议阻塞线程,而是从那个库中受益,并为用户提供一些漂亮的动画,说“等待,响应来”,或者类似的东西,或者一些进度条。

简而言之,不要阻塞线程,通知用户应用程序中发生的事情并让它保持异步。

于 2012-04-12T13:29:43.580 回答
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();
}
于 2013-09-21T18:43:33.213 回答