65.9K
CodeProject 正在变化。 阅读更多。
Home

遍历状态模式

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2023年12月2日

CPOL

2分钟阅读

viewsIcon

5981

在迭代过程中识别已处理实体的模式, 而无需额外的集合。

引言

TraversalStatePattern 模式可用于识别**已处理的实体**,而无需额外的 **集合**,并在完成完整或部分遍历后重置 IsProcessed **标志**,从而无需再次迭代所有节点来清除该标志。它可用于图遍历或处理其他类型的实体。

背景

其思想是在每个 Node 中包含一个 **整数状态** 值,以及一个用于与 Node 的状态进行比较的 **全局整数状态**,以 **确定** **节点是否已被处理**。Node 的状态值将被设置为全局值以 **标记为已处理**。在每次遍历之前,我们递增全局状态数字,这有效地在遍历之前将所有节点重置为未处理状态。

示例场景

我们想要应用模式的图节点示例

/// <summary>
/// This class contains all logic and data related to its basic functionality
/// </summary>
public partial class ExampleNode
{
    public readonly int Value;

    #region Graph hierarchy
    private readonly List<ExampleNode> _children = new();
    public IReadOnlyList<ExampleNode> Children => _children;

    public void AddChild(ExampleNode child)
    {
        _children.Add(child);
    }
    #endregion
}

模式代码

通过使用 partial class,我们可以将所有模式逻辑移至单独的文件

/// <summary>
/// This class contains all pattern members necessary to implement TraversalStatePattern
/// </summary>
public partial class ExampleNode
{
    private static int _globalTraversalState;
    private int _nodeTraversalState = -1;//set to -1 to not be IsProcessed() by default
   
    public static void NewTraversal()
    {
        _globalTraversalState++;
    }

    public void MarkProcessed()
    {
        _nodeTraversalState = _globalTraversalState;
    }

    public bool IsProcessed() => _nodeTraversalState == _globalTraversalState;
}

我们可以将 MarkProcessed()IsProcessed() 方法合并为一个。它只会在使用第一次时返回 'True'

public bool CheckSetProcessed()
{
    if (_nodeTraversalState == _globalTraversalState) //check is processed
        return true;
    _nodeTraversalState = _globalTraversalState;

    return false;
}

模式用法

示例图遍历方法

public void GraphTraversal(ExampleNode root, Action<ExampleNode> procedure)
{
    //Start a new traversal. Now method CheckSetProcessed() in all nodes
    //will return False after this
    ExampleNode.NewTraversal();
    
    var queue = new Queue<ExampleNode>();//Using Queue for BFS 
                                         //(or Stack can be used for DFS traversal)

    //process start node
    queue.Enqueue(root);
    root.CheckSetProcessed();

    while (queue.TryDequeue(out var node))
    {
        foreach (var resultChild in node.Children)
        {
            //Use status pattern
            if (resultChild.CheckSetProcessed())
            {
                continue;//skip node that has been already processed
            }

            //Do some work
            procedure(resultChild);

            //process node children
            queue.Enqueue(resultChild);
        }
    }
}

多线程用法

对于多线程处理,我们应该稍微修改一下代码

public partial class ExampleNode
{
    private static int _globalTraversalState;
    private int _nodeTraversalState = -1;

    public static void NewTraversal()
    {
        _globalTraversalState++;
    }

    public bool CheckSetProcessed()
    {
        var newState = Interlocked.Exchange
                       (ref _nodeTraversalState, _globalTraversalState);

        //if the original value was now equal to global, then node was not processed
        return newState == _globalTraversalState;
    }
}

以下是如何在多线程运行的代码中使用它的示例。我使用了 BackgroundWorkers,但你可以使用其他方法,如 Tasks

public void GraphTraversal(
    ExampleNode root,
    Action<ExampleNode> procedure,
    int threads,
    int nodes)
{
    ExampleNode.NewTraversalMultithreaded(); //Start a new traversal. 
                                             //Now all nodes IsProcessed() will give False

    //Do multithreaded traversal
    var waitEvent = new CountdownEvent(threads);

    var processingBC = new BlockingCollection<ExampleNode> { root };

    for (var i = 0; i < threads; i++)
    {
        var worker = new BackgroundWorker();

        worker.DoWork += (_, _) =>
        {
            foreach (var exampleNode in processingBC.GetConsumingEnumerable())
            {
                procedure(exampleNode);//process node

                if (Interlocked.Decrement(ref nodes) == 0)
                    processingBC.CompleteAdding();

                foreach (var child in exampleNode.Children)
                {
                    if (child.CheckSetProcessedMultithreaded())
                    {
                        continue;//skip node that has been already processed
                    }

                    processingBC.Add(child);
                }
            }
        };

        worker.RunWorkerCompleted += (_, _) =>
        {
            waitEvent.Signal();
            worker.Dispose();
        };
        worker.RunWorkerAsync();
    }

    waitEvent.Wait();
}

并行多线程用法

(并行多线程意味着对同一结构进行多次并发迭代,其中每次迭代都使用多线程运行。)

Git 仓库 包含一个并行、多线程的示例,说明如何使用它,但基准测试表明它比使用 ConcurrentDictionary 快 5-10%。ConcurrentDictionary 方法

不使用额外集合的并行多线程方法,带有位操作和 64 个并行操作的限制:ParallelStatePatternGraphTraversal.cs

NodeParallelUtils.cs(提供线程遍历上下文以跟踪标志位)。

历史

  • 2023年12月3日:初始版本
Traversal State Pattern - CodeProject - 代码之家
© . All rights reserved.