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

时间:2016-05-24 17:22:21

标签: c# asynchronous observablecollection

我使用MSVS 2015编写C#WPF应用程序。我对分层ObservableCollection的异步处理感兴趣。我有一个包含分层元素的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类是Group类和Register类的基类。您可以在Why is InvalideOperationException thrown when I try to serialize to XML an ObservableCollection containing hierarchical elements?上的帖子中查看ProfileElementType,Group和Register类的详细定义。我的集合可以包含Register实例和Group实例。每个Group实例都可以在其ChildProfileElenents中涉及其他Group实例和Register实例 的ObservableCollection。我有一个根组实例,其ChildProfileElenents集合(根集合)中包含所有其他组和寄存器。我编写了一个同步处理根集合的递归函数。这是:

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");
        }
    }
}

外部设备中有很多寄存器 - 不少于2000.因此,这种同步顺序功能由于每次迭代中的类型转换和串口读取超时而工作缓慢。所以我的应用程序在函数工作时挂起。如果你告诉我如何异步编写上述操作,我将非常感激。例如,如何使用TPL中的 Parallel.ForEach 方法或 async / await 方法编写pollDeviceRegisters递归函数。请帮忙。

1 个答案:

答案 0 :(得分:0)

您可以使用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();
}

我没有测试这段代码。

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