Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/304.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用事件使用数据流转发生产者/消费者的异常_C#_Concurrency_Async Await_.net 4.5_Tpl Dataflow - Fatal编程技术网

C# 使用事件使用数据流转发生产者/消费者的异常

C# 使用事件使用数据流转发生产者/消费者的异常,c#,concurrency,async-await,.net-4.5,tpl-dataflow,C#,Concurrency,Async Await,.net 4.5,Tpl Dataflow,我正在尝试使用面向web服务的HTTP请求的数据流实现生产者/消费者队列。我发现,这正好涵盖了这个场景。但是,与Stephen的帖子不同,我不能将生产者队列标记为完整队列,因为客户端应该能够在应用程序的整个生命周期内将请求排队。这种方法背后的思想是,客户机可以不断生成请求,如果有多个请求处于挂起状态(这是必需的),则消费者能够以不同的方式处理请求 这一要求还导致这样一个事实,即在生产完成后不能开始使用请求,而必须在第一个请求排队时开始。这还要求我以非阻塞方式启动消费(否则会导致死锁)。我已经通过

我正在尝试使用面向web服务的HTTP请求的数据流实现生产者/消费者队列。我发现,这正好涵盖了这个场景。但是,与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
        }
    }
    
    公共类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-查找