【问题标题】:How to complete a Channel right way? How to use multiple channels?如何以正确的方式完成频道?如何使用多个渠道?
【发布时间】:2021-10-16 09:00:58
【问题描述】:

我有一个数据列表,想要创建与列表中元素数量相对应的任务数量。但我不知道如何正确完成频道。

我的代码,但通道没有像我预期的那样关闭。

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

namespace Ding.LearningNewThings
{
    public class MultipleChannel
    {
        public static async Task RunMiltipleChannel()
        {
            List<Place> listPlace = Place.InitData();

            Channel<Position> _dataChannel = Channel.CreateUnbounded<Position>();

            var listTask = new Task[11];

            var listStatus = new bool[10];

            for (int k = 0; k < listPlace.Count(); k++)
            {
                var place = listPlace[k];
                listTask[k] = Task.Run(async () =>
                {
                    int kk = k;
                    int count = 0;

                    Random r = new Random();

                    while (count < 10)
                    {
                        int id = r.Next(1, 1000);
                        var position = new Position()
                        {
                            ID = id,
                            Name = $"Postion{id}",
                            PlaceID = place.ID,
                            PlaceName = place.Name
                        };

                        Console.WriteLine($"WRITE: Position ID: {position.ID}, Postion Name: {position.Name}");
                        await _dataChannel.Writer.WriteAsync(position);
                        count++;
                    }

                    lock (listStatus)
                    {
                        if(count == 10)
                        {
                            listStatus[k] = true;
                        }

                        bool isStop = true;
                        
                        foreach(var status in listStatus)
                        {
                            if (!status)
                            {
                                isStop = false;
                            }
                        }

                        if (isStop)
                        {
                            _dataChannel.Writer.Complete();
                            Console.WriteLine("Stopped");
                        }
                    }

                });
            }


            listTask[10] = Task.Run(async () =>
            {
                while (await _dataChannel.Reader.WaitToReadAsync())
                {
                    await Task.Delay(100);

                    var data = await _dataChannel.Reader.ReadAsync();

                    Console.WriteLine($"READ: Position ID: {data.ID}, Postion Name: {data.Name}");
                }
            });

            await Task.WhenAll(listTask);

        }
    }

    public class Place
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public static List<Place> InitData()
        {
            var listData = new List<Place>();

            for (int i = 0; i < 10; i++)
            {
                var data = new Place()
                {
                    ID = i,
                    Name = $"Postion{i}",

                };

                listData.Add(data);
            }
            return listData;
        }
    }

    public class Position
    {
        public int ID { get; set; }
        public int PlaceID { get; set; }
        public string PlaceName { get; set; }
        public string Name { get; set; }

        public static List<Position> InitData()
        {
            var listData = new List<Position>();

            for (int i = 0; i < 10; i++)
            {
                var data = new Position()
                {
                    ID = i,
                    Name = $"Postion{i}"
                };

                listData.Add(data);
            }
            return listData;
        }
    }


}

如果我想为每个任务创建单独的频道,我该如何完成它们?请举个例子。

【问题讨论】:

    标签: c# .net task task-parallel-library system.threading.channels


    【解决方案1】:

    这是修改后的代码。

    我消除了那里的锁和复杂的逻辑。还修复了变量捕获并添加了对读取的完整性检查。

    评论是一致的。请尝试跑步并询问您是否有任何问题。

    public class MultipleChannel
    {
        public static async Task RunMiltipleChannel()
        {
            List<Place> listPlace = Place.InitData();
    
            Channel<Position> _dataChannel = Channel.CreateUnbounded<Position>();
    
            var listTask = new Task[11];
            
            //Count the number of writer tasks that finished
            int completedTasks = 0;
    
            for (int k = 0; k < listPlace.Count; k++)
            {
                var place = listPlace[k];
                //Important to avoid closures
                var kCapture = k;
                listTask[kCapture] = Task.Run(async () =>
                {
                    int kk = kCapture;
                    int count = 0;
    
                    Random r = new Random();
    
                    while (count < 10)
                    {
                        int id = r.Next(1, 1000);
                        var position = new Position()
                        {
                            ID = id,
                            Name = $"Postion{id}",
                            PlaceID = place.ID,
                            PlaceName = place.Name
                        };
    
                        Console.WriteLine($"WRITE: Position ID: {position.ID}, Postion Name: {position.Name}");
                        await _dataChannel.Writer.WriteAsync(position);
                        count++;
                    }
    
                    //Thread-safe check to see if this is the last task to complete
                    if (Interlocked.Increment(ref completedTasks) == 10)
                    {
                        _dataChannel.Writer.Complete();
                        Console.WriteLine($"Task {kk} finished, CHANNEL COMPLETED");
                    }
                    else
                    {
                        Console.WriteLine($"Task {kk} finished");
                    }
    
                });
            }
    
            //Make sure we read everything
            int readCount = 0;
            listTask[10] = Task.Run(async () =>
            {
                while (await _dataChannel.Reader.WaitToReadAsync())
                {
                    await Task.Delay(100);
                    var data = await _dataChannel.Reader.ReadAsync();
                    readCount++;
                    Console.WriteLine($"READ: Position ID: {data.ID}, Postion Name: {data.Name}");
                }
            });
    
            await Task.WhenAll(listTask);
    
            //Sanity check
            Console.WriteLine($"Read {readCount} position data");
        }
    }
    

    我可以确认频道关闭,并读取了 100 个项目。

    【讨论】:

    • Interlocked 和 lock 语句有什么区别?
    • @quanchinhong 这不在讨论范围内,但它消除了很多代码。简而言之,lock 创建了互斥和可能的阻塞以及上下文切换(它是Monitor.EnterMonitor.Exit 的简写)。在这种情况下矫枉过正。 Interlocked 类是线程安全的,通常在 CPU 指令级别实现且非阻塞。与lock 之类的内核级别相反。显然我们需要一些方法来同步访问completedTasksInterlocked.Increment 在这里是完美的并且不会阻塞。这是一种线程安全的增量方式。
    【解决方案2】:

    在任务中使用迭代变量是有问题的。任务完成后我改变了 初始化。例如:

    const int count = 10;
    Task[] tasks = new Task[count];
    for (int i = 0; i < count; i++)
    {
        tasks[i] = Task.Run(() => Console.WriteLine(i));
    }
    await Task.WhenAll(tasks);
    

    将给出以下输出:

    10
    10
    10
    10
    10
    10
    10
    10
    10
    10
    

    锁定没有帮助,因为 i 在到达锁定语句之前发生了变化。

    在循环中使用第二个变量会得到预期的结果:

    const int count = 10;
    Task[] tasks = new Task[count];
    for (int i = 0; i < count; i++)
    {
        int j = i;
        tasks[i] = Task.Run(() => Console.WriteLine(j));
    }
    await Task.WhenAll(tasks);
    
    6
    3
    1
    0
    2
    4
    5
    7
    8
    9
    

    【讨论】:

    • 我要关注频道
    • 这是一个后续错误。 listResult[k] = true; 总是使用最后一个元素(k 是迭代变量)。因此 listResult 永远不会完全设置为 true,因此 isStop 始终为 false 并且永远不会到达 _dataChannel.Writer.Complete();
    • 代码是否运行无异常? k 不设置最后一个元素,它在它之后设置一个。这应该会导致索引异常。
    • 我试过用局部变量代替迭代,但还是没有完成
    • 可能 listPlaces 的元素少于 10 个。为简化起见,我将计算已完成的任务,而不是使用布尔列表。在你的锁中,你增加完成的计数器,然后检查是否完成 == listPlace.Count。
    猜你喜欢
    • 2016-08-21
    • 1970-01-01
    • 2013-06-12
    • 2020-02-02
    • 1970-01-01
    • 2018-11-17
    • 1970-01-01
    • 1970-01-01
    • 2017-11-19
    相关资源
    最近更新 更多