如何发出HTTP请求并等待回调而不阻塞当前线程?

时间:2013-12-09 18:28:05

标签: c# .net asynchronous task-parallel-library httplistener

我们有一个.NET应用程序,它使用基于HTTP的API,我们 POST请求到第三方HTTP端点(不受我们控制 >)和它稍后会在我们提供的HTTP端点上调用我们;类似的东西:

WebRequest request = WebRequest.Create(urlToMethod);
request.Method = @"POST";
request.Headers.Add(@"Callback", "http://ourserver?id="+id ); 

我们制作成千上万的这些电话,所以我们希望尽可能有效(在速度/内存/线程等方面)

就回调代码而言,我们有一个充当监听器的类型;这就是我们启动它的方式:

_httpListener = new HttpListener();
_httpListener.Prefixes.Add(ourServer);
_httpListener.Start();
_httpListener.BeginGetContext(callback, null);

当服务器回拨给我们时,它会点击我们的callback方法,如下所示:

HttpListenerContext context = _httpListener.EndGetContext(result);

HttpListenerResponse httpListenerResponse = context.Response;
httpListenerResponse.StatusCode = 200;
httpListenerResponse.ContentLength64 = _acknowledgementBytes.Length;

var output = httpListenerResponse.OutputStream;
output.Write(_acknowledgementBytes, 0, _acknowledgementBytes.Length);

context.Response.Close();

var handler = ResponseReceived;

if (handler != null)
{
    handler(this, someData);
}

因此,我们有一个侦听器的实例(_which内部使用HttpListener),并且对于它获得的每个响应,通知{{1}上的所有订阅者事件

订阅者(可能数百人)只关心与其特定ResponseReceived相关联的数据。 id看起来像:

subscriber

最后一行让我烦恼。我们发布消息,然后阻止整个线程等待_matchingResponseReceived = new ManualResetEventSlim(false); _listener.WhenResponseReceived += checkTheIdOfWhatWeGetAndSetTheEventIfItMatches; postTheMessage(); _matchingResponseReceived.Wait(someTimeout); 获得响应并调用我们的事件处理程序。我们想使用Listener,但如果我们阻止整个线程等待回调,它似乎不会给我们太多。

是否有更好的(更多 TPL友好)方式来实现这一目标,以便不会阻止任何线程,我们会同时触发更多请求?

2 个答案:

答案 0 :(得分:2)

整个架构似乎比它应该更复杂(我可能没有理解你的程序)。 为什么不将您的请求发布到第二台服务器(BTW,您不需要“POST”字符串文字)并结束例程,然后通过常规Web API方法从该服务器获取请求,解析数据以查找ID ,并为每个ID执行线程?

答案 1 :(得分:2)

async - awaitTaskCompletionSource一起为此做了很多。

发件人方创建TaskCompletionSource,将其添加到字典中(键是请求的ID),发出请求并返回TaskCompletionSource的{​​{1}}。< / p>

接收者然后查看字典以找到正确的Task,将其从那里移除并设置其结果。

发送方法的调用方将TaskCompletionSource返回的await,它将异步等待接收方处理回调调用。

在代码中,它看起来像这样:

Task
// TODO: this probably needs to be thread-safe
// you can use ConcurrentDictionary for that
Dictionary<int, TaskCompletionSource<Result>> requestTcses;

public async Task<Result> MakeApiRequestAsync()
{
   int id = …;
   var tcs = new TaskCompletionSource<Result>();
   requestTcses.Add(id, tcs);
   await SendRequestAsync(id);
   return await tcs.Task;
}

…

var result = await MakeApiRequest();