简体   繁体   English

如何用Observable或async / await包装异步回调?

[英]How to wrap async callback with Observable or async/await?

I am using a network API which works with callbacks. 我正在使用可用于回调的网络API。 So basically, I have a bunch of method calls that I need to use in this 3rd party library which look like this: 因此,基本上,我在第三方库中需要使用大量方法调用,如下所示:

void SendNetworkRequest(string requestType, Action<Response> callback)

I find the code getting a little bit wacky because all of my methods which depend on network resources from this 3rd party API also need to implement callbacks themselves. 我发现代码变得有些古怪,因为依赖于此第三方API网络资源的所有方法也都需要自己实现回调。 For example, in my main scene I may want to get the players information and my code would look something like this: 例如,在我的主场景中,我可能想要获取玩家信息,并且我的代码将如下所示:

void InitMainScene()
{
       _networkHelper.SendNetworkRequest("GetPlayersInfo",OnPlayerInfoResponse);
}

void OnPlayerInfoResponse(Response response)
{
     _playerInfo = response.Info;
}

I have recently gotten into RX and am using it abundently in my code. 我最近进入RX,并在我的代码中大量使用它。 I have read read some about async/await. 我读过一些有关异步/等待的内容。 I have experimented quite a bit, especially with RX, and tried working with Observable.FromAsync() but could not get it to work.. 我已经做了很多试验,尤其是使用RX,并尝试使用Observable.FromAsync(),但无法使其正常工作。

What am I missing here? 我在这里想念什么? How can I write code which is cleaner and doesn't require the callback using RX or async/await? 如何编写更干净并且不需要使用RX或async / await进行回调的代码? The following is the psuedocode which I'm looking for: 以下是我要寻找的伪代码:

        void InitMainScene()
        {
            _playerInfo = Overservable.DoMagicThing<Response>( () => {
_networkHelper.SendNetworkRequest("GetPlayersInfo",(r) =>{return r;}); });
        }

I am not sure about RX. 我不确定RX。 However, you can convert callback-based SendNetworkRequest to an awaitable task like this: 但是,您可以将基于回调的SendNetworkRequest转换为可等待的任务,如下所示:

void SendNetworkRequest(string requestType, Action<Response> callback)
        {
            // This code by third party, this is here just for testing
            if (callback != null)
            { 
                var result = new Response();
                callback(result);
            }
        }

        Task<Response> SendNetworkRequestAsync(string requestType)
        {
            return Task.Run(() =>
            {
                var t = new TaskCompletionSource<Response>();

                SendNetworkRequest(requestType, s => t.TrySetResult(s));

                return t.Task;
            });
        }

Now you can consume SendNetworkRequestAsync with async/await more naturally 现在您可以更自然地使用带有异步/等待的SendNetworkRequestAsync

Here's how to do it with Rx. 这是使用Rx进行操作的方法。 I swapped out Response for string just so I could compile and test, but trivial to swap back.: 我只是将Response换成string ,这样我才能进行编译和测试,但换回来却很简单。

public IObservable<string> SendNetworkRequestObservable(string requestType)
{
    return Observable.Create<string>(observer =>  
    {
        SendNetworkRequest(requestType, s => observer.OnNext(s));
        observer.OnCompleted();
        return Disposable.Empty;
    });
}

// Define other methods and classes here
public void SendNetworkRequest(string requestType, Action<string> callback)
{
    callback(requestType); // implementation for testing purposes
}

Here's my take on doing this with Rx. 这是我对Rx进行此操作的看法。

Inside the NetworkHelper class add this method: NetworkHelper类中,添加以下方法:

public IObservable<Response> SendNetworkRequestObservable(string requestType)
{
    return
        Observable
            .Create<Response>(observer =>
            {
                Action<Response> callback = null;
                var callbackQuery =
                    Observable
                        .FromEvent<Response>(h => callback += h, h => callback -= h)
                        .Take(1);
                var subscription = callbackQuery.Subscribe(observer);
                this.SendNetworkRequest(requestType, callback);
                return subscription;
            });
}

This creates an observable that internally uses Observable.FromEvent(...).Take(1) to create an observable based on expecting a single call to the Action<Response> callback passed to the SendNetworkRequest method. 这将创建一个可观察的对象,该对象内部使用Observable.FromEvent(...).Take(1)来创建一个可观察的对象,这是基于期望传递给SendNetworkRequest方法的对Action<Response> callback的单个调用。

The only issue with this is that the call occurs on the current thread right to completion. 唯一的问题是,该调用发生在当前线程完成权上。 If you want this code to run on a background thread, but return the result to the current thread, then you can do this: 如果希望此代码在后台线程上运行,但将结果返回到当前线程,则可以执行以下操作:

public IObservable<Response> SendNetworkRequestObservable(string requestType)
{
    return
        Observable
            .Start(() =>
                Observable
                    .Create<Response>(observer =>
                    {
                        Action<Response> callback = null;
                        var callbackQuery =
                            Observable
                                .FromEvent<Response>(h => callback += h, h => callback -= h)
                                .Take(1);
                        var subscription = callbackQuery.Subscribe(observer);
                        this.SendNetworkRequest(requestType, callback);
                        return subscription;
                    }))
            .Merge();
}

Either way you'd call it like this: 无论哪种方式,您都可以这样称呼它:

var _playerInfo =
    _networkHelper
        .SendNetworkRequestObservable("GetPlayersInfo")
        .Wait();

Just a side-note: your DoMagicThing is a synchronous call. 只是一个旁注: DoMagicThing是一个同步调用。 In Rx that's a .Wait() call, but it's better to just use a normal .Subscribe when you can. 在Rx中,这是一个.Wait()调用,但是最好在可能的情况下使用普通的.Subscribe


As per the comment, you could do this with async : 根据评论,您可以使用async做到这一点:

async void InitMainScene()
{
    _playerInfo = await _networkHelper.SendNetworkRequestObservable("GetPlayersInfo");
}

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

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