简体   繁体   English

TPL DataFlow 处理异常的正确方法

[英]TPL DataFlow proper way to handle exceptions

I have problem in a windows service which is using TPL DataFlow to manage a queue (database) and redirects work to a grid computing service.我在使用 TPL DataFlow 管理队列(数据库)并将工作重定向到网格计算服务的 windows 服务中遇到问题。 And at one point BufferBlock stops releasing tasks, and I am not sure why.并且在某一时刻 BufferBlock 停止释放任务,我不知道为什么。 I think it's because some exceptions happen during execution of some tasks, but they get suppressed and it's difficult to understand at which point BufferBlock stops accepting new tasks.我认为这是因为在某些任务的执行过程中发生了一些异常,但是它们被抑制了,很难理解 BufferBlock 何时停止接受新任务。

I tried to simplify it in the working example below.我试图在下面的工作示例中简化它。 It doesn't have any exception handling and I and wondering how to properly handle exceptions in TPL.它没有任何异常处理,我想知道如何正确处理 TPL 中的异常。 I found something similar here TPL Dataflow, guarantee completion only when ALL source data blocks completed .我在TPL Dataflow 找到了类似的东西,仅在所有源数据块完成时才保证完成 In this example I have 100 requests, and process data in batches with 10 requests.在此示例中,我有 100 个请求,并以 10 个请求批量处理数据。 Emulating some exception which happens if ID % 9 == 0 If I don't catch this exception, it works a bit and then stops accepting new requests.模拟如果 ID % 9 == 0 时发生的一些异常如果我没有捕获此异常,它会工作一点,然后停止接受新请求。 If I handle and return Result.Failure it works fine I believe, but I'm not sure if it's a proper way to have it in production environment.如果我处理并返回 Result.Failure 我相信它可以正常工作,但我不确定这是否是在生产环境中使用它的正确方法。

I'm new to TPL, forget me if I didn't explain more clearly my question.我是 TPL 的新手,如果我没有更清楚地解释我的问题,请忘记我。 GitHub Project GitHub项目

Image Empty Slots图像空槽

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Timers;
using CSharpFunctionalExtensions;

namespace TestTPL
{
    public class ServicePipeline
    {
        public const int batches = 100;
        private int currentBatch = 0;

        public ServicePipeline(int maxRequestsInParallel)
        {
            MaxRequestsInParallel = maxRequestsInParallel;
        }

        public int MaxRequestsInParallel { get; }
        public BufferBlock<MyData> QueueBlock { get; private set; }
        public List<TransformBlock<MyData, Result>> ExecutionBlocks
            { get; private set; }
        public ActionBlock<Result> ResultBlock { get; private set; }

        private void Init()
        {
            QueueBlock = new BufferBlock<MyData>(new DataflowBlockOptions()
                { BoundedCapacity = MaxRequestsInParallel });
            ExecutionBlocks = new List<TransformBlock<MyData, Result>>();
            ResultBlock = new ActionBlock<Result>(_ => _.OnFailure(
                () => Console.WriteLine($"Error: {_.Error}")));

            for (int blockIndex = 0; blockIndex < MaxRequestsInParallel;
                blockIndex++)
            {
                var executionBlock = new TransformBlock<MyData, Result>((d) =>
                {
                    return ExecuteAsync(d);
                }, new ExecutionDataflowBlockOptions() { BoundedCapacity = 1 });
                executionBlock.LinkTo(ResultBlock, new DataflowLinkOptions()
                    { PropagateCompletion = true });
                QueueBlock.LinkTo(executionBlock, new DataflowLinkOptions()
                    { PropagateCompletion = true });
                ExecutionBlocks.Add(executionBlock);
            }
        }

        public static Result ExecuteAsync(MyData myData)
        {
            //try
            //{
            WebClient web = new WebClient();
            TaskCompletionSource<Result> res = new TaskCompletionSource<Result>();
            Task task = Task<Result>.Run(() => web.DownloadStringAsync(
                new Uri("http://localhost:49182/Slow.ashx")));
            task.Wait();
            Console.WriteLine($"Data = {myData}");
            if (myData != null && myData.Id % 9 == 0)
                throw new Exception("Test");
            return Result.Ok();
            //}
            //catch (Exception ex)
            //{
            //    return Result.Failure($"Exception: {ex.Message}");
            //}
        }

        public async void Start()
        {
            Init();
            while (currentBatch < batches)
            {
                Thread.Sleep(1000);
                await SubmitNextRequests();
            }
            Console.WriteLine($"Completed: {batches}");
        }

        private async Task<int> SubmitNextRequests()
        {
            var emptySlots = MaxRequestsInParallel - QueueBlock.Count;
            Console.WriteLine($"Empty slots: {emptySlots}" +
                $", left = {batches - currentBatch}");
            if (emptySlots > 0)
            {
                var dataRequests = await GetNextRequests(emptySlots);
                foreach (var data in dataRequests)
                {
                    await QueueBlock.SendAsync(data);
                }
            }
            return emptySlots;
        }

        private async Task<List<MyData>> GetNextRequests(int request)
        {
            MyData[] myDatas = new MyData[request];
            Task<List<MyData>> task = Task<List<MyData>>.Run(() =>
            {
                for (int i = 0; i < request; i++)
                {
                    myDatas[i++] = new MyData(currentBatch);
                    currentBatch++;
                }
                return new List<MyData>(myDatas);
            });
            return await task;
        }
    }

    public class MyData
    {
        public int Id { get; set; }
        public MyData(int id) => Id = id;
        public override string ToString() { return Id.ToString(); }
    }
}

EDIT: 10/30/2019 It works as expected when the exception is handled and called explicitly Result.Failure($"Exception: {ex.Message}");编辑:2019年 10 月 30 日当异常被处理并显式调用时,它按预期工作Result.Failure($"Exception: {ex.Message}");

    public static Result ExecuteAsync(MyData myData)
    {
        try
        {
            WebClient web = new WebClient();
            TaskCompletionSource<Result> res = new TaskCompletionSource<Result>();
            Task task = Task<Result>.Run(() => Thread.Sleep(2000));
            task.Wait();
            Console.WriteLine($"Data = {myData}");
            if (myData != null && myData.Id % 9 == 0)
                throw new Exception("Test");
            return Result.Ok();
        }
        catch (Exception ex)
        {
            return Result.Failure($"Exception: {ex.Message}");
        }
    }

When linking two block, there is an option to propagate completion forward, but not backward.链接两个块时,可以选择向前传播完成,但不能向后传播。 This becomes a problem when the BoundedCapacity option is used, and an error occurs, because it can block the feeder of the pipeline and cause a dead-lock.这在使用BoundedCapacity选项时会出现问题,并且会发生错误,因为它会阻塞管道的馈线并导致死锁。 It is quite easy to propagate completion manually though.不过,手动传播完成非常容易。 Here is a method that you can use.这是您可以使用的方法。

async void OnErrorComplete(IDataflowBlock block1, IDataflowBlock block2)
{
    await Task.WhenAny(block1.Completion); // Safe awaiting
    if (block1.Completion.IsFaulted) block2.Complete();
}

It waits asynchronously for block1 to complete, and if it has failed it completes immediately the block2 .它异步等待block1完成,如果失败则立即完成block2 Completing the upstream block is usually enough, but you can also propagate the specific exception if you want:完成上游块通常就足够了,但如果需要,您也可以传播特定的异常:

async void OnErrorPropagate(IDataflowBlock block1, IDataflowBlock block2)
{
    await Task.WhenAny(block1.Completion); // Safe awaiting
    if (block1.Completion.IsFaulted)
        block2.Fault(block1.Completion.Exception.InnerException);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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