简体   繁体   中英

How to wrap async callback with Observable or async/await?

I am using a network API which works with callbacks. 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. 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. 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..

What am I missing here? How can I write code which is cleaner and doesn't require the callback using RX or 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. However, you can convert callback-based SendNetworkRequest to an awaitable task like this:

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

Here's how to do it with Rx. I swapped out Response for string just so I could compile and test, but trivial to swap back.:

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.

Inside the NetworkHelper class add this method:

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.

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. In Rx that's a .Wait() call, but it's better to just use a normal .Subscribe when you can.


As per the comment, you could do this with async :

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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