在自定义任务中添加对调试的支持
适用于:Azure 数据工厂中的 SQL Server SSIS Integration Runtime
使用 Integration Services 运行时引擎可以通过断点在执行过程中将包、任务和其他类型的容器挂起。 通过断点可以检查和纠正那些导致应用程序或任务不能正常运行的错误。 断点体系结构使客户端能够在任务处理被挂起的同时在定义的执行点评估包中对象的运行时值。
自定义任务开发人员可利用此体系结构,使用 IDTSBreakpointSite 接口及其父接口 IDTSSuspend 创建自定义断点目标。 IDTSBreakpointSite 接口定义运行时引擎与用于创建和管理自定义断点位置或目标的任务之间的交互。 IDTSSuspend 接口提供由运行时引擎调用以通知任务挂起或继续执行的方法和属性。
断点位置或目标是任务执行过程中可挂起处理的点。 用户可以从“设置断点”对话框中可用的断点位置中选择。 例如,除了默认断点选项以外,Foreach 循环容器还提供“在每次循环迭代开始时断开”选项。
当任务在执行过程中到达一个断点目标时,它会评估该断点目标,以确定是否已启用断点。 启用断点表明用户希望执行在该断点处停止。 如果启用了断点,则任务会向运行时引擎引发 OnBreakpointHit 事件。 运行时引擎通过调用包中正在运行的每个任务的 Suspend 方法来响应该事件。 当运行时调用挂起任务的 ResumeExecution 方法时,任务将继续执行。
不使用断点的任务仍应实现 IDTSBreakpointSite 和 IDTSSuspend 接口。 这可确保当包中的其他对象引发 OnBreakpointHit 事件时可正确挂起任务。
IDTSBreakpointSite 接口和 BreakpointManager
任务通过调用 CreateBreakpointTarget 的 BreakpointManager 方法,并提供一个整数 ID 和一个字符串说明作为参数来创建断点目标。 当任务在其代码中到达包含断点目标的点时,它会使用 IsBreakpointTargetEnabled 方法评估断点目标,以确定是否已启用断点。 如果为 true,则任务通过引发 OnBreakpointHit 事件来通知运行时引擎。
IDTSBreakpointSite 接口定义了单个方法 AcceptBreakpointManager,运行时引擎在创建任务的过程中调用它。 此方法提供 BreakpointManager 对象作为参数,任务可使用它来创建和管理自己的断点。 任务应将 BreakpointManager 存储在本地,以便在使用 Validate 和 Execute 方法期间使用。
下面的示例代码演示了如何使用 BreakpointManager 创建断点目标。 该示例调用 OnBreakpointHit 方法引发相应的事件。
public void AcceptBreakpointManager( BreakpointManager breakPointManager )
{
// Store the breakpoint manager locally.
this.bpm = breakPointManager;
}
public override DTSExecResult Execute( Connections connections,
Variables variables, IDTSComponentEvents events,
IDTSLogging log, DtsTransaction txn)
{
// Create a breakpoint.
this.bpm.CreateBreakPointTarget( 1 , "A sample breakpoint target." );
...
if( this.bpm.IsBreakpointTargetEnabled( 1 ) == true )
events.OnBreakpointHit( this.bpm.GetBreakpointTarget( 1 ) );
}
Public Sub AcceptBreakpointManager(ByVal breakPointManager As BreakpointManager)
' Store the breakpoint manager locally.
Me.bpm = breakPointManager
End Sub
Public Overrides Function Execute(ByVal connections As Connections, _
ByVal variables As Variables, ByVal events As IDTSComponentEvents, _
ByVal log As IDTSLogging, ByVal txn As DtsTransaction) As DTSExecResult
' Create a breakpoint.
Me.bpm.CreateBreakPointTarget(1 , "A sample breakpoint target.")
If Me.bpm.IsBreakpointTargetEnabled(1) = True Then
events.OnBreakpointHit(Me.bpm.GetBreakpointTarget(1))
End If
End Function
IDTSSuspend 接口
IDTSSuspend 接口定义了运行时引擎在暂停或继续执行任务时调用的方法。 IDTSSuspend 接口由 IDTSBreakpointSite 接口实现,其 Suspend 和 ResumeExecution 方法通常由自定义任务重写。 当运行时引擎收到任务的 OnBreakpointHit 事件时,它会调用每个正在运行的任务的 Suspend 方法,通知任务暂停执行。 当客户端继续执行时,运行时引擎会调用挂起的任务的 ResumeExecution 方法。
挂起和继续执行任务涉及暂停和继续任务的执行线程。 在托管代码中,使用 .NET Framework 的 System.Threading 命名空间中的 ManualResetEvent 类来实现此操作。
下面的代码示例演示任务执行的挂起和继续。 请注意,Execute 方法与上面的代码示例中的该方法不同,触发断点时,它会暂停执行线程。
private ManualResetEvent m_suspended = new ManualResetEvent( true );
private ManualResetEvent m_canExecute = new ManualResetEvent( true );
private int m_suspendRequired = 0;
private int m_debugMode = 0;
public override DTSExecResult Execute( Connections connections, Variables variables, IDTSComponentEvents events, IDTSLogging log, DtsTransaction txn)
{
// While a task is not executing, it is suspended.
// Now that we are executing,
// change to not suspended.
ChangeEvent(m_suspended, false);
// Check for a suspend before doing any work,
// in case the suspend and execute calls
// were initiated at virtually the same time.
CheckAndSuspend();
CheckAndFireBreakpoint( componentEvents, 1);
}
private void CheckAndSuspend()
{
// Loop until we can execute.
// The loop is required rather than a simple If
// because there is a time between the return from WaitOne and the
// reset that we might receive another Suspend call.
// Suspend() will see that we are suspended
// and return. So we need to rewait.
while (!m_canExecute.WaitOne(0, false))
{
ChangeEvent(m_suspended, true);
m_canExecute.WaitOne();
ChangeEvent(m_suspended, false);
}
}
private void CheckAndFireBreakpoint(IDTSComponentEvents events, int breakpointID)
{
// If the breakpoint is enabled, fire it.
if (m_debugMode != 0 && this.bpm.IsBreakpointTargetEnabled(breakpointID))
{
// Enter a suspend mode before firing the breakpoint.
// Firing the breakpoint will cause the runtime
// to call Suspend on this task.
// Because we are blocked on the breakpoint,
// we are suspended.
ChangeEvent(m_suspended, true);
events.OnBreakpointHit(this.bpm.GetBreakpointTarget(breakpointID));
ChangeEvent(m_suspended, false);
}
// Check for a suspension for two reasons:
// 1. If we are at a point where we could fire a breakpoint,
// we are at a valid suspend point. Even if we didn't hit a
// breakpoint, the runtime may have called suspend,
// so check for it.
// 2. Between the return from OnBreakpointHit
// and the reset of the event, it is possible to have
// received a suspend call from which we returned because
// we were already suspended. We need to be sure it is okay
// to continue executing now.
CheckAndSuspend();
}
static void ChangeEvent(ManualResetEvent e, bool shouldSet)
{
bool succeeded;
if (shouldSet)
succeeded = e.Set();
else
succeeded = e.Reset();
if (!succeeded)
throw new Exception("Synchronization object failed.");
}
public bool SuspendRequired
{
get {return m_suspendRequired != 0;}
set
{
// This lock is also taken by Suspend().
// Because it is possible for the package to be
// suspended and resumed in quick succession,
// this property might be set before
// the actual Suspend() call.
// Without the lock, the Suspend() might reset the canExecute
// event after we set it to abort the suspension.
lock (this)
{
Interlocked.Exchange(ref m_suspendRequired, value ? 1 : 0);
if (!value)
ResumeExecution();
}
}
}
public void ResumeExecution()
{
ChangeEvent( m_canExecute,true );
}
public void Suspend()
{
// This lock is also taken by the set SuspendRequired method.
// It prevents this call from overriding an
// aborted suspension. See comments in set SuspendRequired.
lock (this)
{
// If a Suspend is required, do it.
if (m_suspendRequired != 0)
ChangeEvent(m_canExecute, false);
}
// We can't return from Suspend until the task is "suspended".
// This can happen one of two ways:
// the m_suspended event occurs, indicating that the execute thread
// has suspended, or the canExecute flag is set,
// indicating that a suspend is no longer required.
WaitHandle [] suspendOperationComplete = {m_suspended, m_canExecute};
WaitHandle.WaitAny(suspendOperationComplete);
}
Private m_suspended As ManualResetEvent = New ManualResetEvent(True)
Private m_canExecute As ManualResetEvent = New ManualResetEvent(True)
Private m_suspendRequired As Integer = 0
Private m_debugMode As Integer = 0
Public Overrides Function Execute(ByVal connections As Connections, _
ByVal variables As Variables, ByVal events As IDTSComponentEvents, _
ByVal log As IDTSLogging, ByVal txn As DtsTransaction) As DTSExecResult
' While a task is not executing it is suspended.
' Now that we are executing,
' change to not suspended.
ChangeEvent(m_suspended, False)
' Check for a suspend before doing any work,
' in case the suspend and execute calls
' were initiated at virtually the same time.
CheckAndSuspend()
CheckAndFireBreakpoint(componentEvents, 1)
End Function
Private Sub CheckAndSuspend()
' Loop until we can execute.
' The loop is required rather than a simple if
' because there is a time between the return from WaitOne and the
' reset that we might receive another Suspend call.
' Suspend() will see that we are suspended
' and return. So we need to rewait.
Do While Not m_canExecute.WaitOne(0, False)
ChangeEvent(m_suspended, True)
m_canExecute.WaitOne()
ChangeEvent(m_suspended, False)
Loop
End Sub
Private Sub CheckAndFireBreakpoint(ByVal events As IDTSComponentEvents, _
ByVal breakpointID As Integer)
' If the breakpoint is enabled, fire it.
If m_debugMode <> 0 AndAlso Me.bpm.IsBreakpointTargetEnabled(breakpointID) Then
' Enter a suspend mode before firing the breakpoint.
' Firing the breakpoint will cause the runtime
' to call Suspend on this task.
' Because we are blocked on the breakpoint,
' we are suspended.
ChangeEvent(m_suspended, True)
events.OnBreakpointHit(Me.bpm.GetBreakpointTarget(breakpointID))
ChangeEvent(m_suspended, False)
End If
' Check for a suspension for two reasons:
' 1. If we are at a point where we could fire a breakpoint,
' we are at a valid suspend point. Even if we didn't hit a
' breakpoint, the runtime may have called suspend,
' so check for it.
' 2. Between the return from OnBreakpointHit
' and the reset of the event, it is possible to have
' received a suspend call from which we returned because
' we were already suspended. We need to be sure it is okay
' to continue executing now.
CheckAndSuspend()
End Sub
Shared Sub ChangeEvent(ByVal e As ManualResetEvent, ByVal shouldSet As Boolean)
Dim succeeded As Boolean
If shouldSet Then
succeeded = e.Set()
Else
succeeded = e.Reset()
End If
If (Not succeeded) Then
Throw New Exception("Synchronization object failed.")
End If
End Sub
Public Property SuspendRequired() As Boolean
Get
Return m_suspendRequired <> 0
End Get
Set
' This lock is also taken by Suspend().
' Because it is possible for the package to be
' suspended and resumed in quick succession,
' this property might be set before
' the actual Suspend() call.
' Without the lock, the Suspend() might reset the canExecute
' event after we set it to abort the suspension.
SyncLock Me
Interlocked.Exchange(m_suspendRequired,IIf(Value, 1, 0))
If (Not Value) Then
ResumeExecution()
End If
End SyncLock
End Set
End Property
Public Sub ResumeExecution()
ChangeEvent(m_canExecute,True)
End Sub
Public Sub Suspend()
' This lock is also taken by the set SuspendRequired method.
' It prevents this call from overriding an
' aborted suspension. See comments in set SuspendRequired.
SyncLock Me
' If a Suspend is required, do it.
If m_suspendRequired <> 0 Then
ChangeEvent(m_canExecute, False)
End If
End SyncLock
' We can't return from Suspend until the task is "suspended".
' This can happen one of two ways:
' the m_suspended event occurs, indicating that the execute thread
' has suspended, or the canExecute flag is set,
' indicating that a suspend is no longer required.
Dim suspendOperationComplete As WaitHandle() = {m_suspended, m_canExecute}
WaitHandle.WaitAny(suspendOperationComplete)
End Sub