[英]Use events to forward exceptions for producer/consumer using Dataflow
I'm trying to implement a producer/consumer queue using Dataflow for HTTP requests towards a web service. 我正在尝试使用Dataflow为针对Web服务的HTTP请求实现生产者/消费者队列。 I found an excellent post from Stephen Cleary , which is covering exactly this scenario. 我从Stephen Cleary那里找到了一篇很好的文章 ,该文章正是针对这种情况的。 However, in contrast to Stephen's post, I cannot mark the producer queue as complete since clients shall be able to enqueue requests throughout the entire lifetime of the application. 但是,与Stephen的帖子相反,我无法将生产者队列标记为已完成,因为客户端将能够在应用程序的整个生命周期内使请求入队。 The idea behind this approach that the client can constantly produce requests and the consumer is able to handle requests differently if more than 1 request is pending (which is required). 这种方法背后的思想是,如果有多个请求待处理(这是必需的),则客户端可以不断产生请求,而消费者可以以不同方式处理请求。
This requirement leads also to the fact that the consumption of the requests cannot be started after the production was finished, but have to be started the first request was enqueued. 此要求还导致以下事实:请求的消费无法在生产完成后开始,而必须在第一个请求排队后才开始。 This also requires me to start the consumption in a non-blocking way (otherwise it would lead to a deadlock). 这还要求我以非阻塞方式开始使用(否则将导致死锁)。 I've done this via an async-call which is not awaited, which unfortunately hampers the exception handling. 我已经通过未等待的异步调用完成了此操作,不幸的是这妨碍了异常处理。 Exceptions occurring during the consumption (implementing the HTTP requests) cannot bubble up since the call of the consume-function is not awaited. 由于未等待消费功能的调用,因此在消费(实现HTTP请求)期间发生的异常无法冒泡。 I've introduced and event to deal with this kind of problem, but this leads me to the following questions: 我已经介绍了用于解决此类问题的事件,但这将导致以下问题:
To make it a more explicit, I've prepared a code example illustrating the problem I described above: 为了使它更明确,我准备了一个代码示例来说明我上面描述的问题:
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
}
}
Forwarding exceptions via an event has some severe drawbacks: 通过事件转发异常有一些严重的缺点:
For our problem it turned out that it is better to use TaskCompletionSource , which is a standard technique to synchronize different threads. 对于我们的问题,原来最好使用TaskCompletionSource ,这是同步不同线程的一种标准技术。 An instance of TaskCompletionSource
class is provided by each RequestPayload
object. 每个RequestPayload
对象都提供TaskCompletionSource
类的实例。 After the consumption the TaskCompletionSource.Task
is completed (either with the result or with an exception). 使用之后, TaskCompletionSource.Task
完成(带有结果或异常)。 The producer doesn't return the Task for queue.SendAsync(payload)
but payload.CompletionSource.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*/ }
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.