简体   繁体   English

将异步调用转为同步

[英]Turn asynchronous calls into synchronous

Is there any good practice (pattern) in turning asynchronous calls into synchronous? 将异步调用转换为同步是否有任何良好的实践(模式)?
I have a third party library who's methods are all asynchronos, to get result of almoust any method you must listen to an event, which will bring some context with it. 我有一个第三方库,其方法都是异步的,以获得任何方法的结果,你必须听一个事件,这将带来一些上下文。 basically it looks like: 基本上它看起来像:

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

what I need is to execute some code after BeginSomething when it is really complete (thus after OnBeginSomethingCompleted is triggered). 我需要的是在BeginSomething完成后执行一些代码(因此在触发OnBeginSomethingCompleted之后)。 It is very inconvinient to handle the response in the event. 在事件中处理响应非常不方便。

The only way I could think of is running a Thread.Sleep loop and wait till some field on the form is updated, but it doesn't look like very elegant sollution. 我能想到的唯一方法是运行Thread.Sleep循环并等待表单上的某个字段更新,但它看起来不是非常优雅的sollution。

I'm using .net 4.0. 我正在使用.net 4.0。

You could subclass the main class and provide a synchronous version of the operation. 您可以继承主类并提供操作的同步版本。 If subclassing is not an option you could create an extension method. 如果子类化不是一个选项,您可以创建一个扩展方法。 Here is how things might look. 这是事情的样子。

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;
      }
    }
  }
}

Update: 更新:

One thing I should have pointed out is that this could be problematic in some cases. 我应该指出的一点是,在某些情况下这可能会有问题。 Consider this example. 考虑这个例子。

var x = new Subclass();
x.BeginSomething();
x.Something();

It should be obvious that the handler in Something could receive the OnBeginSomethingCompleted event from the previous call to BeginSomething . 很明显, Something中的handler可以从之前的BeginSomething调用中接收OnBeginSomethingCompleted事件。 Make sure you guard against this somehow. 确保你以某种方式防范这一点。

As other said, if possible you should try to make your own code async. 正如其他人所说,如果可能的话,你应该尝试让你自己的代码异步。 If that won't work, does your third-party library support the standard BeginXXX , EndXXX async pattern? 如果这不起作用,您的第三方库是否支持标准的BeginXXXEndXXX异步模式? If so, then using the TPL would make things easy for you. 如果是这样,那么使用TPL会让事情变得简单。 Your code will look something like this: 您的代码看起来像这样:

using System.Threading.Tasks;

...

var task = Task<TResult>.Factory.FromAsync(
    service.BeginSomething, service.EndSomething, arg1, arg2, ..., null);

task.Wait();
var result = task.Result;

The specific overload you'll want to use will depend on how many parameters you need to pass. 您想要使用的特定过载取决于您需要传递多少参数。 You can see the list here . 你可以在这里看到这个列表。

Use a ManualResetEvent . 使用ManualResetEvent In your sync wrapper create it, then pass it to the service.BeginSomething() call as part of the state object. 在同步包装器中创建它,然后将其作为状态对象的一部分传递给service.BeginSomething()调用。 Immediately after the call, WaitOne() on it, this will block. 在调用之后, WaitOne()立即打开,这将阻止。

In the service.OnBeginSomethingCompleted event extract it from the state object and set it, this will unblock the sync caller. service.OnBeginSomethingCompleted事件中从状态对象中提取它并设置它,这将取消阻止同步调用者。

You might want to look at Reactive Extensions 您可能希望查看Reactive Extensions

With Rx you can wrap that into an 'event' basically - the do something like someClass.SomeEvent.Subscribe(d=>...) to subscribe using usually some lambda expression to handle what you need. 使用Rx,您可以基本上将它包装成'事件' - 像someClass.SomeEvent.Subscribe(d=>...) ,通常使用一些lambda表达式来处理您需要的东西。 Also use ObserveOn to handle it on the GUI thread (see the details, this is just a hint). 还可以使用ObserveOn在GUI线程上处理它(请参阅详细信息,这只是一个提示)。

Other option is to use async await (which is now available for use with VS 2010). 其他选项是使用async await (现在可以与VS 2010一起使用)。

hope this helps 希望这可以帮助

NOTE: Rx have a native support for async methods and turning them into Rx events with pretty much just one call. 注意:Rx对异步方法有本机支持,并且只需一次调用就可以将它们转换为Rx事件。 Take a look at Observable.FromAsyncPattern FromAsyncPattern 看一下Observable.FromAsyncPattern FromAsyncPattern

If BeginSomething() returns an IAsyncResult (like a delegate's .BeginInvoke would do), you can get the WaitHandle from that: 如果BeginSomething()返回一个IAsyncResult (就像委托的.BeginInvoke那样),你可以从那里得到WaitHandle

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

By the way, by assigning the event handler after starting the async process, you are introducing a race condition where the async call may complete before the event is registered, causing it to never fire. 顺便说一句,通过在启动异步过程后分配事件处理程序,您将引入一个竞争条件,在该条件下,异步调用可能会在事件注册之前完成,从而导致它永远不会触发。

The general trend of modern software development (on Windows platform too) is to run, what is possible asynchroniously. 现代软件开发的一般趋势(在Windows平台上也是)运行,异步可能。

Actually from Windows8 software design guidelines, if the code runs more then 50ms, it has to be asynchronious. 实际上从Windows8软件设计指南来看,如果代码运行超过50毫秒,它必须是异步的。

So I would not suggest to block the thread, but instead benefit from that library and provide to the user with some nice looking animation saying "wait, responce comming", or something like this, or some progress bar. 因此我不建议阻止该线程,而是从该库中受益并向用户提供一些漂亮的动画,说“等待,响应即将发生”,或类似的东西,或一些进度条。

In short, do not block thread, notify a user about what is going on in app and leave it async. 简而言之,不要阻止线程,通知用户应用程序中发生的事情并保持异步。

This solution is similar to Brian Gideon's, but I think a little bit cleaner for what you're trying to do. 这个解决方案类似于Brian Gideon的解决方案,但我觉得你想要做的事情有点干净。 It uses the Monitor object to cause the calling thread to wait until the Completed event is triggered. 它使用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();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM