C# 如何在不阻塞当前线程的情况下发布HTTP请求并等待回调?

C# 如何在不阻塞当前线程的情况下发布HTTP请求并等待回调?,c#,.net,asynchronous,task-parallel-library,httplistener,C#,.net,Asynchronous,Task Parallel Library,Httplistener,我们有一个.NET应用程序,它使用基于HTTP的API,我们将请求发布到第三方HTTP端点(不在我们的控制之下),并稍后在我们提供的HTTP端点上调用我们;比如: WebRequest request = WebRequest.Create(urlToMethod); request.Method = @"POST"; request.Headers.Add(@"Callback", "http://ourserver?id="+id ); _matchingResponseReceived

我们有一个.NET应用程序,它使用基于HTTP的API,我们
将请求发布到第三方HTTP端点(不在我们的控制之下),并稍后在我们提供的HTTP端点上调用我们;比如:

WebRequest request = WebRequest.Create(urlToMethod);
request.Method = @"POST";
request.Headers.Add(@"Callback", "http://ourserver?id="+id ); 
_matchingResponseReceived = new ManualResetEventSlim(false);
_listener.WhenResponseReceived += checkTheIdOfWhatWeGetAndSetTheEventIfItMatches;
postTheMessage();
_matchingResponseReceived.Wait(someTimeout);
我们打了成千上万个这样的电话,因此我们希望尽可能高效(在速度/内存/线程等方面)

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

_httpListener = new HttpListener();
_httpListener.Prefixes.Add(ourServer);
_httpListener.Start();
_httpListener.BeginGetContext(callback, null);
当服务器回叫我们时,它会点击我们的
回调
方法,该方法如下所示:

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);
}
// 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();
因此,我们有这个侦听器的一个实例(_内部使用
HttpListener
),对于它得到的每个响应,它都会在
ResponseReceived
事件上通知所有订阅者

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

WebRequest request = WebRequest.Create(urlToMethod);
request.Method = @"POST";
request.Headers.Add(@"Callback", "http://ourserver?id="+id ); 
_matchingResponseReceived = new ManualResetEventSlim(false);
_listener.WhenResponseReceived += checkTheIdOfWhatWeGetAndSetTheEventIfItMatches;
postTheMessage();
_matchingResponseReceived.Wait(someTimeout);
最后一句话困扰着我。我们发布消息,但随后阻止整个线程,等待
侦听器获得响应并调用我们的事件处理程序。我们希望使用
Task
s,但如果我们阻塞整个线程等待回调,它似乎不会给我们带来太多好处


有没有更好的(更适合TPL的
)方法来实现这一点,这样就没有线程被阻塞,我们可以同时发出更多请求?

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

async
-
wait
以及
TaskCompletionSource
为此做了很多工作

发送方创建一个
TaskCompletionSource
,将其添加到字典中(键是请求的id),发出请求并返回
TaskCompletionSource
Task

然后,接收者查看字典以找到正确的
TaskCompletionSource
,将其从字典中删除并设置结果

发送方方法的调用方将
等待
返回的
任务
,该任务将异步等待接收方处理回调调用

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

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);
}
// 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();

你会用C#5.0吗?(请记住,您可以在.Net 4.0上使用C#5.0,但您至少需要VS 2012。)@svick,是的,我们可以-它会给我们带来什么?所以问题是关于
\u listener
实例,它看起来像一个自定义类?似乎与HttpListener没有任何关系。这是什么课?你想让你的订阅者在他们自己的线程上运行还是可以使用任务?@PanagiotisKanavos,
\u listener
是我的“侦听”包装器(使用
HttpListener
并在收到响应时将事件发布给订阅方“工作正常,您是明星!如果我不清楚,很抱歉-我们调用的API不在我们的控制之下。我已更新了问题。