Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/24.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

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/lua/3.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# 使用TPL数据流块处理异常_C#_.net_Task Parallel Library_Tpl Dataflow - Fatal编程技术网

C# 使用TPL数据流块处理异常

C# 使用TPL数据流块处理异常,c#,.net,task-parallel-library,tpl-dataflow,C#,.net,Task Parallel Library,Tpl Dataflow,我有一个简单的tpl数据流,它基本上完成了一些任务。 我注意到,当任何数据块中出现异常时,它都不会在初始父块调用程序中被捕获。 我添加了一些手动代码来检查异常,但似乎不是正确的方法 if (readBlock.Completion.Exception != null || saveBlockJoinedProcess.Completion.Exception != null || processBlock1.Completion.Exception != null ||

我有一个简单的tpl数据流,它基本上完成了一些任务。 我注意到,当任何数据块中出现异常时,它都不会在初始父块调用程序中被捕获。 我添加了一些手动代码来检查异常,但似乎不是正确的方法

if (readBlock.Completion.Exception != null
    || saveBlockJoinedProcess.Completion.Exception != null
    || processBlock1.Completion.Exception != null
    || processBlock2.Completion.Exception != null)
{
    throw readBlock.Completion.Exception;
}
我在网上浏览了一下,看看有什么建议,但没有看到任何明显的迹象。 因此,我在下面创建了一些示例代码,希望获得更好解决方案的指导:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

namespace TPLDataflow
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //ProcessB();
                ProcessA();
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception in Process!");
                throw new Exception($"exception:{e}");
            }
            Console.WriteLine("Processing complete!");
            Console.ReadLine();
        }

        private static void ProcessB()
        {
            Task.WhenAll(Task.Run(() => DoSomething(1, "ProcessB"))).Wait();
        }

        private static void ProcessA()
        {
            var random = new Random();
            var readBlock = new TransformBlock<int, int>(x =>
            {
                try { return DoSomething(x, "readBlock"); }
                catch (Exception e) { throw e; }
            }); //1

            var braodcastBlock = new BroadcastBlock<int>(i => i); // ⬅ Here

            var processBlock1 = new TransformBlock<int, int>(x =>
                DoSomethingAsync(5, "processBlock1")); //2
            var processBlock2 = new TransformBlock<int, int>(x =>
                DoSomethingAsync(2, "processBlock2")); //3

            //var saveBlock =
            //    new ActionBlock<int>(
            //    x => Save(x)); //4

            var saveBlockJoinedProcess =
                new ActionBlock<Tuple<int, int>>(
                x => SaveJoined(x.Item1, x.Item2)); //4

            var saveBlockJoin = new JoinBlock<int, int>();

            readBlock.LinkTo(braodcastBlock, new DataflowLinkOptions
                { PropagateCompletion = true });

            braodcastBlock.LinkTo(processBlock1,
                new DataflowLinkOptions { PropagateCompletion = true }); //5

            braodcastBlock.LinkTo(processBlock2,
                new DataflowLinkOptions { PropagateCompletion = true }); //6


            processBlock1.LinkTo(
                saveBlockJoin.Target1); //7

            processBlock2.LinkTo(
                saveBlockJoin.Target2); //8

            saveBlockJoin.LinkTo(saveBlockJoinedProcess,
                new DataflowLinkOptions { PropagateCompletion = true });

            readBlock.Post(1); //10
                               //readBlock.Post(2); //10

            Task.WhenAll(processBlock1.Completion,processBlock2.Completion)
                .ContinueWith(_ => saveBlockJoin.Complete());

            readBlock.Complete(); //12
            saveBlockJoinedProcess.Completion.Wait(); //13
            if (readBlock.Completion.Exception != null
                || saveBlockJoinedProcess.Completion.Exception != null
                || processBlock1.Completion.Exception != null
                || processBlock2.Completion.Exception != null)
            {
                throw readBlock.Completion.Exception;
            }
        }
        private static int DoSomething(int i, string method)
        {
            Console.WriteLine($"Do Something, callng method : { method}");
            throw new Exception("Fake Exception!");
            return i;
        }
        private static async Task<int> DoSomethingAsync(int i, string method)
        {
            Console.WriteLine($"Do SomethingAsync");
            throw new Exception("Fake Exception!");
            await Task.Delay(new TimeSpan(0, 0, i));
            Console.WriteLine($"Do Something : {i}, callng method : { method}");
            return i;
        }
        private static void Save(int x)
        {

            Console.WriteLine("Save!");
        }
        private static void SaveJoined(int x, int y)
        {
            Thread.Sleep(new TimeSpan(0, 0, 10));
            Console.WriteLine("Save Joined!");
        }
    }
}

乍一看,if只有一些不考虑体系结构的要点。在我看来,您混合了一些新的和一些旧的构造。还有一些代码部分是不必要的

例如:

private static void ProcessB()
{
    Task.WhenAll(Task.Run(() => DoSomething(1, "ProcessB"))).Wait();
}
使用Wait方法,如果发生任何异常,它们将被包装在System.AggregateException中。在我看来,这样更好:

private static async Task ProcessBAsync()
{
    await Task.Run(() => DoSomething(1, "ProcessB"));
}
使用async await,如果发生异常,await语句将重新引发包装在System.AggregateException中的第一个异常。这允许您尝试捕获具体的异常类型,并仅处理您真正能够处理的情况

另一件事是代码的这一部分:

private static void ProcessA()
        {
            var random = new Random();
            var readBlock = new TransformBlock<int, int>(
                    x => 
                    { 
                    try { return DoSomething(x, "readBlock"); } 
                    catch (Exception e) 
                    { 
                    throw e; 
                    } 
                    },
                    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1 }); //1

使用等待任务要好得多。延迟。。。。。使用Task.Delay…,您的应用程序将不会冻结。

在第一次查看时,如果您没有查看您的体系结构,那么您的应用程序将不会冻结。在我看来,您混合了一些新的和一些旧的构造。还有一些代码部分是不必要的

例如:

private static void ProcessB()
{
    Task.WhenAll(Task.Run(() => DoSomething(1, "ProcessB"))).Wait();
}
使用Wait方法,如果发生任何异常,它们将被包装在System.AggregateException中。在我看来,这样更好:

private static async Task ProcessBAsync()
{
    await Task.Run(() => DoSomething(1, "ProcessB"));
}
使用async await,如果发生异常,await语句将重新引发包装在System.AggregateException中的第一个异常。这允许您尝试捕获具体的异常类型,并仅处理您真正能够处理的情况

另一件事是代码的这一部分:

private static void ProcessA()
        {
            var random = new Random();
            var readBlock = new TransformBlock<int, int>(
                    x => 
                    { 
                    try { return DoSomething(x, "readBlock"); } 
                    catch (Exception e) 
                    { 
                    throw e; 
                    } 
                    },
                    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 1 }); //1
使用等待任务要好得多。延迟。。。。。使用Task.Delay…,应用程序将不会冻结

我在网上浏览了一下,看看有什么建议,但没有看到任何明显的迹象

如果您或多或少有一条管道,那么常用的方法是使用PropagateCompletion关闭管道。如果有更复杂的拓扑,则需要手动完成块

在您的情况下,您尝试在此处传播:

Task.WhenAll(
    processBlock1.Completion,
    processBlock2.Completion)
    .ContinueWith(_ => saveBlockJoin.Complete());
但此代码不会传播异常。当processBlock1.Completion和processBlock2.Completion都完成时,saveBlockJoin成功完成

更好的解决方案是使用Wait而不是ContinueWith:

使用wait鼓励您处理异常,您可以通过将异常传递给Fault来传播异常

我在网上浏览了一下,看看有什么建议,但没有看到任何明显的迹象

如果您或多或少有一条管道,那么常用的方法是使用PropagateCompletion关闭管道。如果有更复杂的拓扑,则需要手动完成块

在您的情况下,您尝试在此处传播:

Task.WhenAll(
    processBlock1.Completion,
    processBlock2.Completion)
    .ContinueWith(_ => saveBlockJoin.Complete());
但此代码不会传播异常。当processBlock1.Completion和processBlock2.Completion都完成时,saveBlockJoin成功完成

更好的解决方案是使用Wait而不是ContinueWith:


使用await鼓励您处理异常,您可以通过将异常传递给Fault来传播异常。

TPL数据流开箱即用不支持在管道中反向传播错误,这在块具有有限容量时尤其令人讨厌。在这种情况下,块下游的错误可能会导致其前面的块无限期阻塞。我知道的唯一解决方案是使用取消功能,并在任何人失败时取消所有块。以下是如何做到这一点。首先创建一个CancellationTokenSource:

然后逐个创建块,在所有块的选项中嵌入相同的CancellationToken:

var options = new ExecutionDataflowBlockOptions()
    { BoundedCapacity = 10, CancellationToken = cts.Token };

var block1 = new TransformBlock<double, double>(Math.Sqrt, options);
var block2 = new ActionBlock<double>(Console.WriteLine, options);
最后,在出现异常时,使用扩展方法触发取消CancellationTokenSource:

block1.OnFaultedCancel(cts);
block2.OnFaultedCancel(cts);
OnFaultedCancel扩展方法如下所示:

public static class DataflowExtensions
{
    public static void OnFaultedCancel(this IDataflowBlock dataflowBlock,
        CancellationTokenSource cts)
    {
        dataflowBlock.Completion.ContinueWith(_ => cts.Cancel(), default,
            TaskContinuationOptions.OnlyOnFaulted |
            TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
    }
}

TPL数据流开箱即用不支持在管道中向后传播错误,当块具有有限容量时,这尤其令人讨厌。在这种情况下,块下游的错误可能会导致其前面的块无限期阻塞。我知道的唯一解决方案是使用取消功能,并在任何人失败时取消所有块。以下是如何做到这一点。首先创建一个CancellationTokenSource:

然后逐个创建块,在所有块的选项中嵌入相同的CancellationToken:

var options = new ExecutionDataflowBlockOptions()
    { BoundedCapacity = 10, CancellationToken = cts.Token };

var block1 = new TransformBlock<double, double>(Math.Sqrt, options);
var block2 = new ActionBlock<double>(Console.WriteLine, options);
最后,在出现异常时,使用扩展方法触发取消CancellationTokenSource:

block1.OnFaultedCancel(cts);
block2.OnFaultedCancel(cts);
OnFaultedCancel 扩展方法如下所示:

public static class DataflowExtensions
{
    public static void OnFaultedCancel(this IDataflowBlock dataflowBlock,
        CancellationTokenSource cts)
    {
        dataflowBlock.Completion.ContinueWith(_ => cts.Cancel(), default,
            TaskContinuationOptions.OnlyOnFaulted |
            TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
    }
}

如果你对其他选择持开放态度,我会建议你用可观测的方法去接收——它们更容易让你头脑清醒around@KrzysztofSkowronek我也喜欢Rx,但我选择了TPL DF作为一种方式来尝试这项特殊任务:如果您愿意接受替代方案,我建议你用可观察的方式去接收——它们更容易让你头脑清醒around@KrzysztofSkowronek我也喜欢Rx,但我选择了TPL DF作为一种方式来尝试这项特殊任务:像这样,感觉自然。像这样,感觉自然。