[英]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项目
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.