1

I'm working on c# project. I'd like to send API request via websocketsharp library in some sort of synchronous way.

I've been trying to do it following way:

  1. Before we send any WS request , we create new SynchronousRequest() object with unique ID and add the newly created object to some sort of waiting list

  2. We send WS request adding unique ID to the payload, on the response - the server will return the same id.

  3. We start waiting for the event to be signaled (signaling happens once we receive response)

On the response handler:

  1. Once WS response arrives, I try to match the context by the unique ID
  2. Once it's matched, we signal event that the response has been received and add the response payload to the the synchronousRequest() object

Problem is step 3, once i use WaitOne() on the event the entire websocket client hangs and no further responses will be received - resulting in complete deadlock.

How can i do some sort of WaitOne() call in seperate thread or perhaps completely better solution exists for my problem, so the entire client does not hang and we match the contexts?

public class SynchronousRequest
{
    public long requestId;
    public ManualResetEvent resetEvent = new ManualResetEvent(false);
    public dynamic response;

    public SynchronousRequest()
    {
        var random = new Random();
        requestId = random.Next();
    }

}


public class APIWebSocket: BaseAPIWebSocket
{


    private List<SynchronousRequest> waitingSyncRequests = new List<SynchronousRequest>();


    public APIWebSocket()
    {
        ws = new WebSocket("wss://www.someserver.com");

        registerConnectionEvents(); //Registers onOpen(), onMessage() handlers and similar
    }



    public void SendSyncTest()
    {
        var sr = new SynchronousRequest();
        waitingSyncRequests.Add(sr);

        //some data to send
        var msg = new
        {
            jsonrpc = "2.0",
            method = "public/ticker",
            id = sr.requestId, //Response should contain the same ID
            @params = new
            {
                instrument_name = "ETH"
            }
        };

        ws.Send(JsonConvert.SerializeObject(msg));

        //Below WaitOne() causes the entire websocket connection/thread to block
        // No further messages will be received by HandleMessage() once we call WaitOne()

        sr.resetEvent.WaitOne(); //Wait until we receive notification that response has been received

        //do some processing on response here... 

       //Synchronous request completed, remove it from list
       waitingSyncRequests.Remove(sr);
    }




    protected override void OnReceivedMessage(System.Object sender, WebSocketSharp.MessageEventArgs e)
    {
        dynamic message = JsonConvert.DeserializeObject(e.Data);

        if (message.id != null )
        {
            //Find a resetEvent for given message.id
            var matchingSyncRequest = waitingSyncRequests.First(r => r.requestId == message.id);
            if (matchingSyncRequest != null)
            {
                matchingSyncRequest.response = message;
                matchingSyncRequest.resetEvent.Set(); //Notify that response has been received
            }
        }

    }

}
Josh
  • 81
  • 1
  • 3
  • 7
  • So you want to wait for something that is out of your control.Have you considered using `TaskCompletionSource` ? You create a request , you create a `tcs` , you send the request over the socket on a thread , you add the `tcs` to a queue and you await the `tcs`.On another thread/task you receive messages.You `dequeue` tcs from said queue and use `tcs.SetResult(websocketReceivedMessage)` – Bercovici Adrian Jul 16 '20 at 10:30

1 Answers1

0

From what i understand you need to await on some event that will be set at some future time. You could consider using TaskCompletionSource.

  • 1.Whenever you send a message that you need its result , you could create a TaskCompletionSource (tcs) , add it to a Dictionary and send the message over the socket.

  • 2.You await tcs.Task or tcs.Task.Result on that tcs.

  • 3.On another Thread/Task you could process your incoming responses ( in your case you have your received message handler. Whenever you receive a response of a target type or id , you could just fetch the tcs from the dictionary based on the id, and Task.SetResult(response) it.In that moment the caller (the one you are awaiting gets unblocked).

     public class Message
     {
       public string ID{get;set;}
     }
     public class Response
     {
         public string Id{get;set;}
     }
    
     public void MyClass
     {
        private ConcurrentDictionary<string,TaskCompletionSource<Response>>map=new ConcurrentDictionary<string,TaskCompletionSource<Response>>();
        private Websocket socket;
        public void SomeEventTrigger()
        {
            var msg=new Message{ Id="somespecialID" };
            var tcs=new TaskCompletionSource<Response>();
            ws.Send(JsonConvert.SerializeObject(msg));
            if(!this.map.TryAdd(msg.Id,tcs))
            {
               return;
            }
            var result=tcs.Result; //this gets blocked until you use `tcs.SetResult`- >  that would happen in your OnReceivedMessage  
            this.map.TryRemove(msg.Id,out TaskCompletionSource<Response>resp);
    
        }
        protected override void OnReceivedMessage(System.Object sender, WebSocketSharp.MessageEventArgs e)
        {
             Response message = JsonConvert.DeserializeObject<Response>(e.Data);
    
             if (message.id != null )
             {
    
                 if(this.map.TryGetValue(message.id),out TaskCompletionSource<Response> tcs)
                 {
                    tcs.SetResult(message); //this unblocks the method that wrote the message to the socket   (above)
                 }
    
    
             }
          }
       }
    

P.S You need to make sure that you use Task.SetResult on the correct tcs so that the correct call to SomeEventTrigger method stops waiting.

Bercovici Adrian
  • 8,794
  • 17
  • 73
  • 152