2

我在 DigitalMicrograph 中创建了两个线程,它们在脚本执行后立即执行。
我想要一些不同的东西。

让我们想象一下线程的两个按钮(启动和停止线程)。
如何添加代码以仅在按下按钮时激活线程?

如果您有我的代码示例,那将非常有帮助。

4

1 回答 1

2

有几件事需要考虑:

  • 您不能从 UIframe 对象中分配新对象。(更准确地说:从由 UI 操作调用的方法。您可以在构造函数中分配 fe 或在开始时使用 Init() 方法。)因此,您事先分配它们,然后让 UIframe 对象知道它们。

  • 您经常希望 UIframe 对象知道线程对象,但也希望线程对象知道 UIframe 对象。(这样如果线程对象中的某些内容需要,UI 可以更改。)

  • 将对象作为对象的成员变量有点危险,因为这些对象只有在“保留”对象被释放后才能被释放。如果两个对象作为成员变量相互持有,那么你就处于死锁状态!出于这个原因,使用弱引用是节省的:只保留objectID编号作为成员变量并在需要时查找对象。

以下代码示例应该为您提供一个起点。它由 2 个类和一个主调用组成。代码在此答案中拆分,只需将其复制并粘贴到单个脚本文件中进行测试。

首先是线程对象:

class myThread:Thread
{
  number linkedDLG_ID
  number externalBreak

  myThread( object self )  
  {
    result( self.ScriptObjectGetID() + " created.\n" )
  }

  ~myThread( object self )
  {
    result( self.ScriptObjectGetID() + " destroyed.\n" )
  }

  void SetLinkedDialogID( object self, number ID ) { linkedDLG_ID = ID; }
  void InterruptAtNextChance( object self ) { externalBreak = 1; }

  void RunThread( object self )
  {
    number maxLoop = 30

    object callDLG = GetScriptObjectFromID( linkedDLG_ID )
    externalBreak = 0
    for( number i=0; i<maxLoop; i++ )
    {
      sleep( 0.1 )
      Result( i + "\n" )
      if ( callDLG.ScriptObjectIsValid() )
      {
        callDLG.DLGSetProgress( "progress", (i+1)/maxLoop )
        callDLG.ValidateView()
      }

      if ( externalBreak )
        break;
    }

    // Cleanup at end of thread
    if ( callDLG.ScriptObjectIsValid() )
    {
      callDLG.DLGSetProgress( "progress", 0 )
      callDLG.LookUpElement( "DBevel" ).DLGValue( 0 )
      callDLG.ValidateView( )
    }
  }
}
  • 任何线程类都派生自Thread类。

  • 该类有两个成员变量。一个将保存 UI 对象的 ID,另一个是一个简单的布尔值,允许“外部”调用来停止正在运行的线程。

  • 前两个方法是构造函数和析构函数。在本示例中它们并不是真正需要的,但在脚本开发期间将它们放入是一种很好的做法,因为它们将在结果窗口中指示该类的对象何时被创建以及何时被销毁。这有助于跟踪内存泄漏和死锁情况。

  • 接下来的两个方法允许“外部”调用来设置两个成员变量。

  • RunThread方法是任何Thread类的核心。它必须完全是这个签名,因为它覆盖了父类Thread的相应方法,我们从中派生出MyThread类。当调用StartThread()方法时, RunThread方法将启动到单独的后台线程中。(StartThread()是类Thread的一个方法。)

  • RunThread中的实际代码分为两部分:

    1. 一个“动作循环”可以做任何你想做的事情,但如果布尔变量改变值则允许快速退出。这就是外部呼叫可以中断的方式。这将在下面讨论。

    2. 对象可以影响 UI 对象的“清理”部分,下面也会讨论。

接下来是 UI 类:

class myDialog:UIframe
{
  object callThread

  myDialog( object self )
  {
    result( self.ScriptObjectGetID() + " created.\n" )
  }
  ~myDialog( object self )
  {
    result( self.ScriptObjectGetID() + " destroyed.\n")
  }


  TagGroup CreateDLG( object self )
  {
    image i := IntegerImage( "", 1, 0, 25, 25)
    i = 0; i[ 2 , 2 , 23 , 23 ] = 1;
    image onImage, offImage
    onImage   = RGB( 0*i , 200*i , 0*i )
    offImage  = RGB( 200*i , 0*i , 0*i )

    TagGroup tgItems, tg, button, label, progress
    tg = DLGCreateDialog("Dialog",tgItems)
    button = DLGCreateDualStateBevelButton( "DBevel", onImage, offImage, "StartPressed" )
    progress = DLGCreateProgressBar( "progress" ).DLGfill( "X" )
    label = DLGCreateLabel( "start/stop" )
    tgItems.DLGAddElement( DLGGroupItems( button , label ).DLGTableLayout( 2 , 1 , 0 ) )
    tgItems.DLGAddElement( progress )
    return tg
  }

  object Init(object self, number callThreadID )
  {
    // Assign thread-object via weak-reference
    callThread = GetScriptObjectFromID( callThreadID )      
    if ( !callThread.ScriptObjectIsvalid() )
      Throw( "Invalid thread object passed in! Object of given ID not found." )

    // Pass weak-reference to thread object
    callThread.SetLinkedDialogID( self.ScriptObjectGetID() )  
    return self.super.init( self.CreateDLG() )
  }

  void StartPressed( object self )
  {
    number active = self.LookupElement( "DBevel" ).DLGGetValue()
    if ( active )
      callThread.StartThread()
    else
      callThread.InterruptAtNextChance()
  } 
}
  • 任何对话框 (UI) 类都派生自UIframe类。

  • 这个类只有一个成员变量:一个对象,它将是线程对象。

  • 同样有一个构造函数/析构函数方法可以更轻松地调试。

  • CreateDLG方法构建描述对话框的 tagGroup 。我不会在这里详细介绍,但本质上它会在显示时创建以下对话框:

    显示的对话框

  • Init()方法初始化对象。基类UIframeInit()方法需要一个描述性的 TagGroup 并返回 UI 对象本身。我们在扩展的Init()方法的最后一行调用它,并使用我们的类方法来创建 tagGroup:

    return self.super.init( self.CreateDLG() )

    之前的代码将我们的线程对象链接到 UI 对象。我们传入一个数字,这是我们的线程对象的对象 ID。我们现在从内存中获取相应的对象并将其分配给我们的本地成员变量。(注意:变量现在保存对象本身,而不是它的副本或克隆!)

    callThread = GetScriptObjectFromID( callThreadID )

    马上,我们检查查找是否成功并且实际上返回了一个有效的对象。如果没有,我们会抛出异常来停止我们的脚本。从现在开始,UI 对象“包含”线程对象并可以使用它。

  • 现在是反向链接。现在已经分配了 UI 对象,它还有一个对象 ID。我们将这个数字输入到我们的线程对象中。

    callThread.SetLinkedDialogID( self.ScriptObjectGetID() )

    从现在开始,线程对象很好地链接到 UI 对象。回顾myThread类,您会注意到我们使用了相同的技巧,即在RunThread()方法中查找和本地存储对象。

  • StartPressed()是链接到我们的对话框按钮的方法。这个按钮是斜角按钮,所以我们查询它的状态,也就是斜角按钮改变后的状态,并采取相应的行动。我们要么启动线程对象的 RunThread() 方法作为后台线程,要么调用相应的“中断”方法,它只是设置布尔变量

最后是主脚本:

void StartScript()
 {
   object threadObject = alloc( myThread )
   object dlgObject = alloc( myDialog ).Init( threadObject.ScriptObjectGetID() )
   dlgObject.display( "Dialog" )
 }
StartScript()
  • 这里没有很多事情发生。我们首先创建 myThread 类的 threadObject,然后创建对话框 UI 对象。

  • 我们用现有threadObject的 ID 初始化对话框对象,然后将其显示为无模式对话框。

需要注意的几点:

  • 每当您在 DigitalMicrograph 脚本中使用对象变量时,都应将它们放入结构块中。这可确保在离开结构块时对象超出范围并被删除。在主脚本中定义和分配的对象变量在脚本结束时不会被破坏。出于这个原因,我们将主脚本封装在一个方法本身中。

  • 在此示例中,我们使用了两种不同的链接方法:

    • 直接myDialog类确实将线程对象本身保留为成员变量。尽管我们仅使用 ID 对其进行了初始化,但我们立即将对象链接到成员变量。

    • 弱引用myThread类仅将对话对象的对象 ID 作为成员变量保存。

    我们为什么这样做?如果myThread类将对话对象保留为成员,那么这两个对象将在死锁情况下相互保持。两者都不能因为另一个而被破坏。但是为什么我们没有对myDialog类使用相同的方法呢?因为我们想在后台线程本身中将对话框显示为无模式对话框!

    想想主脚本:

    1. 我们创建线程对象
    2. 我们创建对话对象
    3. 我们显示对话框对象(但我们不会在这里停止脚本执行!
    4. 脚本结束

    但是当脚本结束时,对象变量threadObjectdlgObject超出范围!它们将立即被销毁,除非有什么东西将它们保存在内存中。dlgObject保留在内存中,因为我们将其显示为无模式对话框。当相应的对话窗口关闭时,它将被释放。但是是什么保留了threadObject?没有!一旦RunThread()方法完成,它将被释放并随后被销毁。但是,因为它是dlgObject的成员,所以它不会被破坏。

于 2014-08-26T09:10:47.370 回答