将异步调用转换为同步调用

本文关键字:调用 同步 转换 异步 | 更新日期: 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 事件中,将其从状态对象中提取并设置,这将取消阻止同步调用方。

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

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

如果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();
}