主题:多线程应用程序示范,利用挂起事件

概述:本示例展示如何利用应用程序的挂起事件,从外部工作线程与 Revit API 进行通信。

项目文件:

Command.cs

这个文件包含了继承自 IExternalCommand 接口并实现了 Execute 方法的 Command 类。

Application.cs

这个文件包含了继承自 IExternalApplication 接口并实现了其方法的 Application 类。

FaceAnalyzer.cs

这个文件处理了对一个墙面进行分析结果的显示。 

SharedResults.cs

这个文件用于分析器展示结果和计算结果之间的交换。这里的所有操作都是线程安全的。 

ThreadAgent.cs

这个被委托线程的主要类所包含的数据对计算很有帮助,它有一个在单独线程上运行的方法。

描述:

本示例展示了如何利用应用程序的挂起事件,从外部工作线程与 Revit API 进行通信。

说明:

1. 打开 Revit 应用程序并创建一个新文档;

2. 创造一面简单的墙;

3. 进入 3D 视图并放大,以显示墙壁最大的一面;

4. 启动外部命令“分析墙面”;

5. 当命令进入选择模式时,单击墙面上的主面;

6. 随着结果出现在墙面上,分析开始;

7. 过程需要大约12秒的时间;

8. 在此期间,您可以修改墙壁或添加门窗;

9. 每当修改墙壁时,分析将重新开始(还需12秒)。

完整的源代码请加入QQ群649037449,在群文件中下载RevitSDK.exe,解压后在文件夹中搜索本文中应用程序名称即可获得完整源码

FaceAnalyzer.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//
using System;
using System.Threading;
using System.Collections.Generic;

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB.Analysis;

namespace Revit.SDK.Samples.WorkThread.CS
{
/// <summary>
/// This class handles displaying results of an analysis of a wall surface.
/// </summary>
/// <remarks>
/// The analyzer only displays the results, it does not calculate them.
/// The calculation is delegated to a work-thread. The analyzer kicks
/// off the calculation and then returns to Revit. Periodically Revit
/// checks (during an Idling event) if there is more results available.
/// If there is, the analyzer grabs them from the work-thread and takes
/// care of the visualization. The thread lets the analyzer to know when
/// the calculation is finally over. When that happens, the application
/// can unregister from Idling since it does not need it anymore.
/// <para>
/// Optionally, Revit can interrupt the analysis, which it typically does
/// when the element being analyzed changes (or gets deleted). Depending
/// on that change the application requests the analysis to restart
/// or to stop completely. The analyzer makes sure the requests
/// are delivered to the work-thread.
/// </para>
/// </remarks>
///
class FaceAnalyzer
{
#region class member variables
// ActiveView
private View m_view = null;
// string representation of reference object
private String m_sreference = null;
// SpatialFieldManager
private SpatialFieldManager m_SFManager = null;
// schema Id
private int m_schemaId = -1;
// field Id
private int m_fieldId = -1;
// ThreadAgent
private ThreadAgent m_threadAgent = null;
// Results
private SharedResults m_results;
// If the result manager needs to be initialized
private bool m_needInitialization = true;
#endregion

#region class member methods
/// <summary>
/// Constructor
/// </summary>
///
public FaceAnalyzer(View view, String sref)
{
m_sreference = sref;
m_view = view;
}

/// <summary>
/// Simple convention method to get an actual reference object
/// from its stable representation we keep around.
/// </summary>
private Reference GetReference()
{
if ((m_view != null) && (m_sreference.Length > 0))
{
return Reference.ParseFromStableRepresentation(m_view.Document, m_sreference);
}
return null;
}

/// <summary>
/// Getting the face object corresponding to the reference we have stored
/// </summary>
/// <remarks>
/// The face may change during the time it takes our calculation to finish,
/// thus we always get it from the reference right when we actually need it;
/// </remarks>
private Face GetReferencedFace()
{
Reference faceref = GetReference();
if (faceref != null)
{
return m_view.Document.GetElement(faceref).GetGeometryObjectFromReference(faceref) as Face;
}
return null;
}

/// <summary>
/// Getting ready to preform an analysis for the given in view
/// </summary>
/// <remarks>
/// This initializes the Spatial Field Manager,
/// adds a field primitive corresponding to the face,
/// and registers our result schema we want to use.
/// The method clears any previous results in the view.
/// </remarks>
///
public void Initialize()
{
// create of get field manager for the view

m_SFManager = SpatialFieldManager.GetSpatialFieldManager(m_view);
if (m_SFManager == null)
{
m_SFManager = SpatialFieldManager.CreateSpatialFieldManager(m_view, 1);
}

// For the sake of simplicity, we remove any previous results

m_SFManager.Clear();

// register schema for the results

AnalysisResultSchema schema = new AnalysisResultSchema("E4623E91-8044-4721-86EA-2893642F13A9", "SDK2014-AL, Sample Schema");
m_schemaId = m_SFManager.RegisterResult(schema);

// Add a spatial field for our face reference

m_fieldId = m_SFManager.AddSpatialFieldPrimitive(GetReference());

m_needInitialization = false;
}

/// <summary>
/// Returns the Id of the element being analyzed
/// </summary>
/// <value>
/// Id of the element of which face the analysis was set up for.
/// </value>
///
public ElementId AnalyzedElementId
{
get
{
Reference faceref = GetReference();

if (faceref != null)
{
return faceref.ElementId;
}

return ElementId.InvalidElementId;
}
}

/// <summary>
/// Updating results on the surface being analyzed
/// </summary>
/// <remarks>
/// This is called periodically by the Idling event
/// until there is no more results to be updated.
/// </remarks>
/// <returns>
/// Returns True if there is still more to be processed.
/// </returns>
public bool UpdateResults()
{
// If we still need to initialize the result manager
// it means we were interrupted and we restarted.
// Therefore we know we do not have any results to pick up.
// Instead, we initialize the manager and start a new calculation.
// We will have first results from this new calculation process
// next time we get called here.

if (m_needInitialization)
{
Initialize();
return StartCalculation();
}

// We aks the Result instance if there are results available
// The methods returns True only if there has been results added
// since the last time we called that method.

IList<UV> points;
IList<ValueAtPoint> values;

if (m_results.GetResults(out points, out values))
{
FieldDomainPointsByUV fieldPoints = new FieldDomainPointsByUV(points);
FieldValues fieldValues = new FieldValues(values);
m_SFManager.UpdateSpatialFieldPrimitive(m_fieldId, fieldPoints, fieldValues, m_schemaId);
}

// if the thread is not around anymore to result more data,
// it means the analysis is finished for the FaceAnalyzer too.

return ((m_threadAgent != null) && (m_threadAgent.IsThreadAlive));
}

/// <summary>
/// Starting a work-thread to perform the calculation
/// </summary>
/// <returns>
/// True if the thread started off successfully.
/// </returns>
public bool StartCalculation()
{
// we need to get the face from the reference we track
Face theface = GetReferencedFace();
if (theface == null)
{
return false;
}

// An instance for the result exchange
m_results = new SharedResults();

// The agent does not need the face nor the reference.
// It can work with just the bounding box and a density of the grid.
// We also pass the Results as an argument. The thread will be adding calculated results
// to that object, while here in the analyzer we will read from it. Both operations are thread-safe;

m_threadAgent = new ThreadAgent(theface.GetBoundingBox(), 10, m_results);

// now we can ask the agent to start the work thread
return m_threadAgent.Start();
}

/// <summary>
/// Stopping the calculation if still in progress
/// </summary>
/// <remarks>
/// This is typically to be called when there have been changes
/// made in the model resulting in changes to the element being analyzed.
/// </remarks>
///
public void StopCalculation()
{
// We first signal we do not want more results,
// so the work-thread knows to stop if it is still around.

m_results.SetCompleted();

// If the thread is alive, we'll wait for it to finish
// It will not take longer than one calculation cycle.

if (m_threadAgent != null)
{
if (m_threadAgent.IsThreadAlive)
{
m_threadAgent.WaitToFinish();
}
m_threadAgent = null;
}
}

/// <summary>
/// Restarting the calculation.
/// </summary>
/// <remarks>
/// This is probably caused by a change in the face being analyzed,
/// typically when the DocumentChanged event indicates modification were made.
/// </remarks>
///
public void RestartCalculation()
{
// First we make sure we start the ongoing calculation
StopCalculation();

// For this might have been called during times when the document
// is in a non-modifiable state (e.g. during an undo operation)
// we cannot really start the new calculation just yet. We can only
// set a flag that a re-initialization is yet to be made,
// which will be then picked up at the next update. That will happen
// on a regular Idling event, during which the document is modifiable.
m_needInitialization = true;
}
#endregion

} // class

}

SharedResults.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc. All rights reserved.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL ITS FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable. 
using System;
using System.Collections.Generic;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Analysis;
namespace Revit.SDK.Samples.WorkThread.CS
{
    /// <summary>
    ///   This class is for exchange of results between the
    ///   analyzer which displays the results and the work-thread
    ///   that calculates them. All operations are thread-safe;
    /// </summary>
    /// 
    class SharedResults
    {
        #region class member variables
        // List of ValueAtPoints
        private IList<ValueAtPoint> m_values = new List<ValueAtPoint>();
        // List of UV points
        private IList<UV> m_points = new List<UV>();
        // lock object
        private Object mylock = new Object();
        // If completed
        private bool m_completed = false;
        // Last read number
        private int m_NumberWhenLastRead = 0;
        #endregion
        #region class member methods
        /// <summary>
        ///   Signaling no more results are needed
        /// </summary>
        /// <remarks>
        ///   This is set by the analyzer if it needs no more data.
        ///   We will let this know to the work-thread when it attempts
        ///   to add more results. When the work-tread results are not
        ///   needed anymore, it will stop even when not finished yet
        ///   and returns (which basically means it will die).
        /// </remarks>
        /// 
        public void SetCompleted()
        {
            lock (mylock)
            {
                m_completed = true;
            }
        }
        /// <summary>
        ///   Returns a list of points and values acquired so far.
        /// </summary>
        /// <returns>
        ///   False if there have been no new results acquired from
        ///   the work-thread since the last time this method was called.
        /// </returns>
        /// 
        public bool GetResults(out IList<UV> points, out IList<ValueAtPoint> values)
        {
            bool hasMoreResults = false;
            points = null;
            values = null;
            lock (mylock)    // lock the access
            {
                hasMoreResults = (m_values.Count != m_NumberWhenLastRead);
                if (hasMoreResults)
                {
                    points = m_points;
                    values = m_values;
                    m_NumberWhenLastRead = m_values.Count;
                }
            }
            return hasMoreResults;
        }
        /// <summary>
        ///   Adding one pair of point/value to the collected
        ///   results for the current analysis.
        /// </summary>
        ///   The work-thread calls this every time it has another result to add.
        /// <returns>
        ///   Returns False if no more values can be accepted, which signals 
        ///   to the work-thread that the analysis was interrupted and
        ///   that the thread is supposed to stop and return immediately.
        /// </returns>
        /// 
        public bool AddResult(UV point, double value)
        {
            bool accepted = false;
            lock (mylock)    // lock the access
            {
                // do nothing if reading has been completed
                if (!m_completed)
                {
                    // First, the double is converted to a one-item
                    // list of ValueAtPoint. Than the list is added
                    // to the list of values, while the UV is added
                    // to the list of points.
                    List<double> doubleList = new List<double>();
                    doubleList.Add(value);
                    m_values.Add(new ValueAtPoint(doubleList));
                    m_points.Add(point);
                    accepted = true;
                }
            }
            return accepted;
        }
        #endregion
    }  // class
}

ThreadAgent.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//
using System;
using System.Threading;
using Autodesk.Revit.DB;
namespace Revit.SDK.Samples.WorkThread.CS
{
    /// <summary>
    ///   A main class of the delegated thread.
    /// </summary>
    /// <remarks>
    ///   It has few data the calculations needs and
    ///   one method that will run on a separate thread.
    /// </remarks>
    /// 
    class ThreadAgent
    {
        #region class member variables
        // The main method for calculating results for the face analysis
        private Thread m_thread = null;
        // Results
        private SharedResults m_results;
        // BoundingBoxUV
        private BoundingBoxUV m_bbox;
        // Density
        private int m_density;
        #endregion
        #region class member methods
        /// <summary>
        ///  A constructor initializes a bounding box and
        ///  the density of the grid for the values to be calculated at.
        /// </summary>
        /// 
        public ThreadAgent(BoundingBoxUV bbox, int density, SharedResults results)
        {
            m_bbox = bbox;
            m_density = density;
            m_results = results;
        }
        /// <summary>
        ///   Creates and starts a work thread operating upon the given  shared results.
        /// </summary>
        /// <returns>
        ///   True if a work thread could be started successfully.
        /// </returns>
        /// 
        public bool Start()
        {
            if (IsThreadAlive)
            {
                return false;
            }
            m_thread = new Thread(new ParameterizedThreadStart(this.Run));
            m_thread.Start(m_results);
            return true;
        }
        /// <summary>
        ///   A property to test whether the calculation thread is still alive.
        /// </summary>
        /// 
        public bool IsThreadAlive
        {
            get
            {
                return (m_thread != null) && (m_thread.IsAlive);
            }
        }
        /// <summary>
        ///   Waits for the work thread to finish
        /// </summary>
        /// 
        public void WaitToFinish()
        {
            if (IsThreadAlive)
            {
                m_thread.Join();
            }
        }
        /// <summary>
        ///   The main method for calculating results for the face analysis.
        /// </summary>
        /// <remarks>
        ///   The calculated values do not mean anything particular.
        ///   They are just to demonstrate how to process a potentially
        ///   time-demanding analysis in a delegated work-thread.
        /// </remarks>
        /// <param name="data">
        ///   The instance of a Result object to which the results
        ///   will be periodically delivered until we either finish
        ///   the process or are asked to stop.
        /// </param>
        /// 
        private void Run(Object data)
        {
            SharedResults results = data as SharedResults;
            double uRange = m_bbox.Max.U - m_bbox.Min.U;
            double vRange = m_bbox.Max.V - m_bbox.Min.V;
            double uStep = uRange / m_density;
            double vStep = vRange / m_density;
            for (int u = 0; u <= m_density; u++)
            {
                double uPos = m_bbox.Min.U + (u * uStep);
                double uVal = (double)(u * (m_density - u));
                for (int v = 0; v <= m_density; v++)
                {
                    double vPos = m_bbox.Min.V + (v * vStep);
                    double vVal = (double)(v * (m_density - v));
                    UV point = new UV(uPos, vPos);
                    double value = Math.Min(uVal, vVal);
                    // We pretend the calculation of values is far more complicated
                    // while what we really do is taking a nap for a few milliseconds 
                    Thread.Sleep(100);
                    // If adding the result is not accepted it means the analysis
                    // have been interrupted and we are supposed to get out ASAP
                    if (!results.AddResult(point, value))
                    {
                        return;
                    }
                }
            }  // for
        }
        #endregion
    }  // class
}