简体   繁体   English

使用事件通过Dataflow向生产者/消费者转发异常

[英]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: 我已经介绍了用于解决此类问题的事件,但这将导致以下问题:

  1. Is it a good idea to use an event to forward exceptions from the consumer to the client of the producer? 使用事件将异常从消费者转发到生产者的客户端是一个好主意吗?
  2. Is this a good idea to implement the producer/consumer pattern in that fashion for my use case? 以这种方式为我的用例实现生产者/消费者模式是一个好主意吗?
  3. Are there potentially other approaches, which are more beneficial under the given circumstances? 是否存在其他可能在给定情况下更有利的方法?

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: 通过事件转发异常有一些严重的缺点:

  • Natural exception handling is not possible. 自然异常处理是不可能的。 If developers are aware of this mechanism, they won't catch any exception. 如果开发人员知道此机制,他们将不会捕获任何异常。
  • You cannot use AppDomain#UnhandledException for unhandled exceptions during the application runtime. 在应用程序运行时,不能将AppDomain#UnhandledException用于未处理的异常。 In fact, if you don't have a subscription to the 'Exception'-event, the exception is completely lost. 实际上,如果您没有订阅“例外”事件,则该例外将完全丢失。
  • If you have only one event to subscribe to, your exception object needs a lot of context information in order to figure out which operation caused the exception. 如果只有一个事件要订阅,则您的异常对象需要大量上下文信息,以便找出导致异常的操作。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM