异步等待另一个请求而不阻塞

时间:2016-06-14 10:39:30

标签: c# .net asynchronous async-await task

我有一个websocket应用程序,它是一个OWIN中间件。当请求进入时,会启动websocket处理程序的新实例,然后在这样的循环中等待icoming消息:

var buffer = new byte[1024*64];
Tuple<ArraySegment<byte>, WebSocketMessageType> received;
do
{
    received = await _webSocket.ReceiveMessage(buffer, _cancellationToken.Token);
    if (received.Item1.Count > 0 && someConditionForCallingDoSomething)
    {
        await DoSomething(received.Item1);
    }
    else if(isAnswer)
    {
        QueueAnswer(received.Item1);
    }
} while (received.Item2 != WebSocketMessageType.Close);

当数据可用时,_webSocket.ReceiveMessage返回的任务将完成。

DoSomething处理其数据,然后通过websocket连接发送内容。然后它应该通过websocket连接等待消息。处理完此消息后,它应该做一些工作并返回(任务)。 也许这个小图解释了它更容易:

_______________
| DoSomething |
|-------------|
|    Work  ---------> Send WS message
|             |
|     ??      |
|             |
|  More Work <------- Receive WS message
|   |         |
|   V         |
|  return;    |
|_____________|

Do some work with the data
          |
          V
     Send a message
          |
          V
   Wait for an answer
          |
          V
    Process answer
          |
          V
        finish

我试图等待答案:

var answerCancel = new CancellationTokenSource();
answerCancel.CancelAfter(30 * 1000);

var answer = await Task.Run(async () => 
    {
        string tmpAnswer = null;

        while (!_concurrentAnswerDict.TryGetValue(someKey, out tmpAnswer)) {
            await Task.Delay(150, answerCancel.Token);
        }

        return tmpAnswer;
    }, answerCancel.Token);

但这似乎会阻止,直到任务被取消。当我调试程序时,我在30秒后看到QueueAnswer的调用。我想,Task.Run将在一个新线程中运行该函数,但它似乎没有。从Task.Run阻塞的角度来看,我觉得它不合理,因为我等待DoSomething的执行,因此接收新消息也会被阻止。

我的问题是:我如何实现这样的行为?在完成之前,如何使DoSomething等待另一个websocket消息?

提前感谢您的每一个提示

的Lukas

2 个答案:

答案 0 :(得分:2)

首先,我建议使用SignalR,因为他们会为你处理很多这些难题。但如果你想自己做,请继续阅读...

此外,我假设“工作”和“回答”消息可以按任何顺序到达同一个Web套接字,并且您正在使用_concurrentAnswerDict来协调来自的“传出”问题消息DoSomething收到“回复”消息。

在这种情况下,您将需要一个独立于DoSomething的“websocket reader”任务;您无法让读者await DoSomething,因为这样会阻止您阅读答案。我认为这是你遇到的主要问题。

这是 await任务可以接受的极少数情况之一。假设DoSomething将捕获自己的异常并处理日志记录等等,那么我们可以将其视为独立的“main”并忽略它返回的任务:

var buffer = new byte[1024*64];
Tuple<ArraySegment<byte>, WebSocketMessageType> received;
do
{
  received = await _webSocket.ReceiveMessage(buffer, _cancellationToken.Token);
  if (received.Item1.Count > 0 && someConditionForCallingDoSomething)
  {
    var _ = DoSomething(received.Item1);
  }
  else if(isAnswer)
  {
    QueueAnswer(received.Item1);
  }
} while (received.Item2 != WebSocketMessageType.Close);

这应该允许QueueAnswerDoSomething尚未完成时运行。

  

我想,Task.Run将在一个新线程中运行该函数,但它似乎没有。从Task.Run阻塞的角度来看,我认为它无法正常工作,因为我等待DoSomething的执行,因此接收新消息也将被阻止。

Task.Run 在另一个线程中运行。但DoSomething(异步)等待它完成,并且读取循环(异步)等待DoSomething完成,然后才能读取下一条消息。

其他说明:

while (!_concurrentAnswerDict.TryGetValue(someKey, out tmpAnswer)) {
  await Task.Delay(150, answerCancel.Token);
}

这对我来说似乎很奇怪。我建议使用TaskCompletionSource<Answer>代替Answer的密钥字典。然后,QueueAnswer将调用TaskCompletionSource<Answer>.SetResult,此代码将等待TaskCompletionSource<Answer>.Task(如果需要超时,则等待Task.Delay。)

答案 1 :(得分:0)

您应该使用信号r并在客户端接收响应。 或者最好在客户端发送请求并在客户端接收。

在向服务器发送请求并等待响应时,使用await会阻塞。