C# 使用事件使用数据流转发生产者/消费者的异常
我正在尝试使用面向web服务的HTTP请求的数据流实现生产者/消费者队列。我发现,这正好涵盖了这个场景。但是,与Stephen的帖子不同,我不能将生产者队列标记为完整队列,因为客户端应该能够在应用程序的整个生命周期内将请求排队。这种方法背后的思想是,客户机可以不断生成请求,如果有多个请求处于挂起状态(这是必需的),则消费者能够以不同的方式处理请求 这一要求还导致这样一个事实,即在生产完成后不能开始使用请求,而必须在第一个请求排队时开始。这还要求我以非阻塞方式启动消费(否则会导致死锁)。我已经通过一个未等待的异步调用完成了这项工作,不幸的是,这会妨碍异常处理。在消费过程中发生的异常(实现HTTP请求)不能冒泡,因为消费函数的调用没有等待。我已经介绍了处理此类问题的方法和事件,但这让我想到了以下问题:C# 使用事件使用数据流转发生产者/消费者的异常,c#,concurrency,async-await,.net-4.5,tpl-dataflow,C#,Concurrency,Async Await,.net 4.5,Tpl Dataflow,我正在尝试使用面向web服务的HTTP请求的数据流实现生产者/消费者队列。我发现,这正好涵盖了这个场景。但是,与Stephen的帖子不同,我不能将生产者队列标记为完整队列,因为客户端应该能够在应用程序的整个生命周期内将请求排队。这种方法背后的思想是,客户机可以不断生成请求,如果有多个请求处于挂起状态(这是必需的),则消费者能够以不同的方式处理请求 这一要求还导致这样一个事实,即在生产完成后不能开始使用请求,而必须在第一个请求排队时开始。这还要求我以非阻塞方式启动消费(否则会导致死锁)。我已经通过
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
}
}
公共类HttpConnector
{
专用缓冲块队列;
公共委托void ConnectorExceptionHandler(对象发送方,异常e);
公共事件ConnectorExceptionHandler ConnectorExceptionOccessed;
公共任务ProduceRequest(请求有效负载)
{
if(this.queue==null)
{
this.queue=new BufferBlock();
this.consumerRequestsAsync(queue);//无法等待此调用,因为它将导致死锁
//但是,通过不等待此调用,在
//ConsumerRequestsAsync将丢失
}
返回等待队列。SendAsync(有效负载)
}
公共任务ConsumerRequestsAsync(缓冲块队列)
{
while(wait queue.OutputAvailableAsync())
{
尝试
{
var payload=wait queue.ReceiveAsync();
//请执行HTTP请求。。。
}
捕获(例外e)
{
ConnectorExceptionOccured(this,e);//触发事件将异常转发给客户端
}
}
}
}
公共类客户端
{
专用HttpConnector连接器=新HttpConnector();
公共任务UpdateDataAsync()
{
连接器+=(对象发送方,异常e)//注册事件处理程序以接收发生的异常
//在使用请求期间
{
//处理异常或重新抛出
};
connector.ProduceRequest(new RequestPayload());//生成请求
}
}
通过事件转发异常有一些严重的缺点:
- 自然异常处理是不可能的。如果开发人员知道这种机制,他们就不会捕获任何异常
- 在应用程序运行时,不能对未处理的异常使用。事实上,如果您没有对“Exception”事件的订阅,则该异常将完全丢失
- 如果只有一个事件要订阅,那么exception对象需要大量上下文信息,以便找出导致异常的操作
RequestPayload
对象都提供了TaskCompletionSource
类的实例。消费后,TaskCompletionSource.Task
完成(结果或异常)。生产者不会为队列.sendaync(有效负载)
返回任务,而是有效负载.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*/ }
}
}
公共类请求有效负载
{
公共IModelBase有效负载{get;set;}
public TaskCompletionSource CompletionSource{get;private set;}
}
公共类HttpConnector
{
专用缓冲块队列;
公共任务ProduceRequest(请求有效负载)
{
if(this.queue==null)
{
this.queue=new BufferBlock();
this.consumerRequestsAsync(队列);
}
wait queue.sendaync(有效负载);
返回wait payload.CompletionSource.Task;
}
公共任务ConsumerRequestsAsync(缓冲块队列)
{
while(wait queue.OutputAvailableAsync())
{
尝试
{
var payload=wait queue.ReceiveAsync();
//请执行HTTP请求。。。
payload.CompletionSource.TrySetResult(null);
}
捕获(例外e)
{
payload.CompletionSource.TrySetException(e)
}
}
}
}
公共类客户端
{
专用HttpConnector连接器=新HttpConnector();
公共任务UpdateDataAsync()
{
尝试
{
等待连接器.ProduceRequest(新的RequestPayload());
}
catch(异常e){/*句柄异常*/}
}
}
为什么不在结果中包含异常?因此,围绕可能失败的代码(如预期的那样)包装一个try/catch
,并返回Success
或Failure
(当然,您需要一个基类或其他东西)-我一直在这样做(它是FP 101-查找或)