我正在尝试使用Dataflow实现针对Web服务的HTTP请求的生产者/消费者队列。我找到了an excellent post from Stephen Cleary,它正好涵盖了这个场景。但是,与Stephen的帖子相反,我无法将生产者队列标记为完整,因为客户端应该能够在应用程序的整个生命周期内将请求排队。这种方法背后的想法是客户端可以不断产生请求,并且如果有多个请求待处理(这是必需的),则消费者能够以不同方式处理请求。
此要求还导致在生产完成后无法启动请求的消耗,但必须在第一个请求入队时启动。这也要求我以非阻塞的方式开始消费(否则会导致死锁)。我通过异步调用完成了这个,这是不等待的,不幸的是阻碍了异常处理。在消费期间发生的异常(实施HTTP请求)不会冒泡,因为不等待消费函数的调用。我已经介绍了处理这类问题的事件,但这引出了以下问题:
为了使它更明确,我准备了一个代码示例来说明我上面描述的问题:
public class HttpConnector
{
private BufferBlock<RequestPayload> queue;
public delegate void ConnectorExceptionHandler(object sender, Exception e);
public event ConnectorExceptionHandler ConnectorExceptionOccured;
public Task<bool> ProduceRequest(RequestPayload payload)
{
if(this.queue == null)
{
this.queue = new BufferBlock<RequestPayload>();
this.ConsumeRequestsAsync(queue); //this call cannot be awaited since it would lead to a deadlock
//however, by not awaiting this call all exceptions raised in
//ConsumeRequestsAsync will be lost
}
return await queue.SendAsync(payload)
}
public Task ConsumeRequestsAsync(BufferBlock<RequestPayload> queue)
{
while(await queue.OutputAvailableAsync())
{
try
{
var payload = await queue.ReceiveAsync();
//do the HTTP request...
}
catch (Exception e)
{
ConnectorExceptionOccured(this, e); //fire event to forward the exception to the client
}
}
}
}
public class Client
{
private HttpConnector connector = new HttpConnector();
public Task<bool> UpdateDataAsync()
{
connector += (object sender, Exception e ) //register an event handler to receive exceptions occur
//during the consumption of the requests
{
//handle exception or re-throw
};
connector.ProduceRequest(new RequestPayload()); //produce a request
}
}
答案 0 :(得分:0)
通过事件转发异常有一些严重的缺点:
对于我们的问题,事实证明最好使用TaskCompletionSource,这是一种同步不同线程的标准技术。每个TaskCompletionSource
对象提供RequestPayload
类的实例。消费后,TaskCompletionSource.Task
完成(结果或异常)。制作人不会返回queue.SendAsync(payload)
但payload.CompletionSource.Task
的任务:
public class RequestPayload
{
public IModelBase Payload { get; set; }
public TaskCompletionSource<IResultBase> CompletionSource { get; private set; }
}
public class HttpConnector
{
private BufferBlock<RequestPayload> queue;
public Task ProduceRequest(RequestPayload payload)
{
if(this.queue == null)
{
this.queue = new BufferBlock<RequestPayload>();
this.ConsumeRequestsAsync(queue);
}
await queue.SendAsync(payload);
return await payload.CompletionSource.Task;
}
public Task ConsumeRequestsAsync(BufferBlock<RequestPayload> queue)
{
while(await queue.OutputAvailableAsync())
{
try
{
var payload = await queue.ReceiveAsync();
//do the HTTP request...
payload.CompletionSource.TrySetResult(null);
}
catch (Exception e)
{
payload.CompletionSource.TrySetException(e)
}
}
}
}
public class Client
{
private HttpConnector connector = new HttpConnector();
public Task UpdateDataAsync()
{
try
{
await connector.ProduceRequest(new RequestPayload());
}
catch(Exception e) { /*handle exception*/ }
}
}