C# 如何异步迭代包含分层元素的ObservableCollection?

C# 如何异步迭代包含分层元素的ObservableCollection?,c#,asynchronous,observablecollection,C#,Asynchronous,Observablecollection,我使用MSVS2015编写C#WPF应用程序。我对分层ObservaleCollection的异步处理感兴趣。我有一个ObservableCollection实例,其中包含层次结构元素: public class Group : ProfileElementType { . . . . . . const ushort deviceAddress = 1; . . . . . . private ObservableCollection<ProfileEle

我使用MSVS2015编写C#WPF应用程序。我对分层ObservaleCollection的异步处理感兴趣。我有一个ObservableCollection实例,其中包含层次结构元素:

public class Group : ProfileElementType
{
    . . . . . .
    const ushort deviceAddress = 1;
    . . . . . .
    private ObservableCollection<ProfileElementType> _childProfileElenents;
    [Browsable(false)]
    public ObservableCollection<ProfileElementType> ChildProfileElenents
    {
        get { return this._childProfileElenents; }
        set { this_childProfileElenents = value; }
    }
    public Group()
    {
        . . . . .
        this.ChildProfileElenents = new ObservableCollection<ProfileElementType>();
        . . . . .
    }
    . . . . .
}
公共类组:ProfileElementType
{
. . . . . .
const ushort deviceAddress=1;
. . . . . .
私人可观察收集(childprofileelements);;
[可浏览(错误)]
公共可观察集合子档案元素
{
获取{返回此。\u ChildProfileElements;}
设置{this_childprofileelements=value;}
}
公共组()
{
. . . . .
this.childProfileElements=新的ObservableCollection();
. . . . .
}
. . . . .
}
ProfileElementType类是Group类和Register类的基类。您可以在我的文章《我的收藏》中看到ProfileElementType、Group和Register类的详细定义,它们可以包括Register实例和Group实例。每个组实例都可以涉及其他组实例,并在其ChildProfileElement中注册实例 可观察到的收集。我有一个根组实例,它的ChildProfileElements集合(根集合)中有所有其他组和寄存器。我编写了一个递归函数,可以同步处理根集合。这是:

private void pollDeviceRegisters(ObservableCollection<ProfileElementType> collection)
{
    if (collection == null)
       return;
    foreach (ProfileElementType elem in collection)
    {
        if (elem.ElementTypeName == "Group")
        {
            Group group = elem as Group;
            pollDeviceRegisters(group.ChildProfileElenents);
        }
        else
        {
            Register reg = elem as Register;
            // Get this register' value from outer device via serial port using MODBUS RTU protocol.
            ushort[] aRes = ReadHoldingRegisters(deviceAddress, reg.Number, 1);
            reg.CurrentValue = aRes[0].ToString("X");
        }
    }
}
private void pollDeviceRegisters(ObservableCollection集合)
{
if(集合==null)
返回;
foreach(集合中的ProfileElementType elem)
{
如果(elem.ElementTypeName==“组”)
{
组=元素作为组;
PolleDeviceRegister(儿童档案组);
}
其他的
{
寄存器reg=作为寄存器的元素;
//使用MODBUS RTU协议,通过串行端口从外部设备获取该寄存器的值。
ushort[]aRes=读取保持寄存器(设备地址,注册号,1);
reg.CurrentValue=aRes[0].ToString(“X”);
}
}
}

在外部设备中有很多寄存器——不少于2000个。因此,由于每次迭代中的类型转换和串行端口读取超时,此synchronouse顺序函数工作缓慢。因此,当函数运行时,我的应用程序挂起。如果您能告诉我如何异步编写上述操作,我将不胜感激。例如,如何使用TPL中的Parallel.ForEach方法或async/await方法编写PollDeviceRegister递归函数。请提供帮助。

您可以使用BlockingCollection读取串行端口,并将该部分移动到新线程(如果这是瓶颈)

private Task pollDeviceRegisters(ICollection<ProfileElementType> collection)
{
    var blockingCollection = new BlockingCollection<Register>();

    // This starts three threads to start reading the serial ports (arbitrary number of threads to start you will need to see what is optimal)
    var pollingTask = Task.WhenAll(ReadSerialPort(blockingCollection),
        ReadSerialPort(blockingCollection),
        ReadSerialPort(blockingCollection))
        .ContinueWith(task =>
        {
            blockingCollection.Dispose();
            return task;
        });

    BuildDeviceRegisters(collection, blockingCollection);

    return pollingTask;
}

// Creates a new thread and waits on the blocking collection
private Task ReadSerialPort(BlockingCollection<Register> collection)
{
    return Task.Run(() =>
    {
        foreach (var reg in collection.GetConsumingEnumerable())
        {
            // Get this register' value from outer device via serial port using MODBUS RTU protocol.
            var aRes = ReadHoldingRegisters(deviceAddress, reg.Number, 1);
            reg.CurrentValue = aRes[0].ToString("X");
        }
    });
}

// flattens the hierarchical data
private static void BuildDeviceRegisters(ICollection<ProfileElementType> collection,
    BlockingCollection<Register> blockingCollection)
{
    if (collection != null)
    {
        //using a stack instead of recursion (could switch to ConcurrentStack and use Parallel.ForEach() but wouldn't mess with it if not bottle neck)
        var stack = new Stack<ICollection<ProfileElementType>>(new[] {collection});
        while (stack.Count > 0)
        {
            var element = stack.Pop();
            foreach (var item in element)
            {
                var group = item as Group;
                if (group != null)
                {
                    // push the tree branch on to the stack
                    stack.Push(group.ChildProfileElenents);
                }
                else
                {
                    blockingCollection.Add((Register)item);
                }
            }
        }
    }

    // Mark we are done adding to the collection
    blockingCollection.CompleteAdding();
}
专用任务轮询设备注册表(ICollection集合)
{
var blockingCollection=new blockingCollection();
//这将启动三个线程开始读取串行端口(任意数量的线程开始,您将需要了解什么是最佳的)
var pollingTask=Task.WhenAll(ReadSerialPort(blockingCollection)),
ReadSerialPort(blockingCollection),
ReadSerialPort(blockingCollection))
.ContinueWith(任务=>
{
blockingCollection.Dispose();
返回任务;
});
BuildDeviceRegister(收集、阻止收集);
返回轮询任务;
}
//创建新线程并等待阻塞集合
专用任务ReadSerialPort(BlockingCollection集合)
{
返回任务。运行(()=>
{
foreach(collection.getconsumineGenumerable()中的var reg)
{
//使用MODBUS RTU协议,通过串行端口从外部设备获取该寄存器的值。
var aRes=读取保持寄存器(设备地址,注册号,1);
reg.CurrentValue=aRes[0].ToString(“X”);
}
});
}
//展平分层数据
专用静态void BuildDeviceRegisters(ICollection集合,
封锁收集封锁收集)
{
if(集合!=null)
{
//使用堆栈而不是递归(可以切换到ConcurrentStack并使用Parallel.ForEach(),但如果不是瓶颈,也不会弄乱它)
var stack=newstack(new[]{collection});
而(stack.Count>0)
{
var元素=stack.Pop();
foreach(元素中的var项)
{
var group=项目作为组;
如果(组!=null)
{
//将树枝推到堆栈上
stack.Push(group.childprofileelements);
}
其他的
{
blockingCollection.Add((注册)项);
}
}
}
}
//马克:我们已经完成了对收藏的添加
blockingCollection.CompleteAdding();
}
我没有测试这个代码


此外,这将返回一个任务,该任务需要等待以了解串行端口的读取何时完成。一旦您开始使用TPL,您将看到许多方法需要更改为异步并等待。如果向集合中添加太多内容并占用大量内存,则还可以限制向阻止集合中添加内容,这将使add方法阻塞,直到消费者从集合中获取信息。

伙计们,我当然想请原谅,但在我的例子中使用async Wait将非常棒。有人能介绍一下吗?除非你读串口软件的方式有任务方法(ReadHoldingRegisters中的代码是什么),否则这可能是你用async Wait得到的最好的方法。当你调用pollDeviceRegisters时,你应该使用wait修饰符并使该方法成为异步的,然后在你的代码中随处可见?我是否可以在外部代码中定义CancellationToken实例,将“foreach(collection.GetConsumingEnumerable()中的var reg){}”更改为“while(collection.TryTake(out reg,50,cancella