Alexandros Pappas
С помощью Microsoft я наконец понял, как работает тайм-аут транзакции COM+. Тайм-аут транзакции COM+ - это тайм-аут транзакции, а не вызова метода. Метод будет выполняться до тех пор, пока он должен выполняться. В тот момент, когда первая транзакция зачислена, тайм-аут транзакции начинает работать. Если транзакция занимает больше времени, чем указанный тайм-аут, транзакция помечается для прерывания, но не отменяется, она продолжает выполняться. Когда метод COM+ вызывает SetComplete, только тогда возникает исключение. Исключение таймаута заключается в следующем:
Система.Время Выполнения.InteropServices.ComException: {"корневая транзакция хотела совершить фиксацию, но транзакция прервалась (исключение из HRESULT: 0x8004E002)"}.
Транзакция регистрируется, как только открывается соединение с базой данных или как только что-то записывается в очередь сообщений.
Предположим, что тайм-аут транзакции составляет 10 секунд. Рассмотрим следующие 3 случая:
1. метод COM+ немедленно подключает транзакцию, которая занимает 11 секунд.
В этом случае исключение тайм-аута генерируется через 11 секунд, поскольку транзакция заняла больше времени, чем тайм-аут.
2. Метод COM+ спит в течение 6 секунд (или выполняет некоторые вычисления), а затем подключает транзакцию, которая занимает 5 секунд.
В этом случае метод завершается без ошибок, хотя общее время выполнения также составляет 11 секунд.
3. Метод COM+ немедленно подключает транзакцию, которая занимает 5 секунд, а затем спит в течение 6 секунд.
В этом случае исключение тайм-аута генерируется через 11 секунд, поскольку время между зачислением транзакции и вызовом SetComplete составляет 11 секунд, что больше тайм-аута.
Таким образом, если время между первым зачислением транзакции и вызовом SetComplete больше тайм-аута транзакции, то возникает исключение тайм-аута. Однако метод COM+ всегда выполняется до тех пор, пока он должен выполняться, независимо от времени ожидания транзакции.
Чтобы проверить вышеперечисленные случаи, вот код в VB.NET:
1. проект Test1BO.vbproj как библиотека классов, подписанная сильным ключом, имеет два файла:
1.1 файле AssemblyInfo.ВБ:
Imports System.EnterpriseServices
Imports System.Reflection
Imports System.Runtime.InteropServices
<Assembly: ApplicationActivation(ActivationOption.Server)>
<Assembly: ApplicationAccessControl(CType(AccessChecksLevelOption.Application, Boolean))>
<Assembly: Guid("799facfd-af56-4496-bc18-618e2522e5f7")>
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyFileVersion("1.0.0.0")>
1.2 ClassComPlus.vb
Imports System.EnterpriseServices
Imports System.Data.SqlClient
Imports System.Transactions
<Transaction(TransactionOption.Required, isolation:=TransactionIsolationLevel.Serializable, timeout:=10), _
EventTrackingEnabled(True), _
JustInTimeActivation(True)>
Public Class ClassComPlus
Inherits ServicedComponent
Public Function DbExecuteNonQuery(
connectionString As String, cmdText As String,
sleepSecondsBefore As Integer, sleepSecondsAfter As Integer) As Integer
Try
Threading.Thread.Sleep(sleepSecondsBefore * 1000)
Dim result = 0
Using cn = New SqlConnection(connectionString)
cn.Open()
Dim cmd = New SqlCommand(cmdText, cn)
result = cmd.ExecuteNonQuery()
End Using
Threading.Thread.Sleep(sleepSecondsAfter * 1000)
ContextUtil.SetComplete()
Return result
Catch
ContextUtil.SetAbort()
Throw
End Try
End Function
Test1BO должен быть зарегистрирован в COM+, например, со следующим файлом RegisterComPlus.bat в папке, где создается dll:
set regsvcs=C:\Windows\Microsoft.NET\Framework\v4.0.30319\regsvcs
set topdir=%~dp0
set dllname=Test1BO
%regsvcs% /u "%topdir%\%dllname%.dll"
%regsvcs% "%topdir%\%dllname%.dll"
pause
2. проект Test1CA.vbproj как консольное приложение со ссылкой на Test1BO.dll имеет только Module1.vb:
Module Module1
Private Sub MyTrace(s As String)
Console.WriteLine(String.Format("{0} {1}", Now.ToString("yyyy-MM-dd HH:mm:ss"), s))
End Sub
Private Sub TestDbExecNonQuery()
Dim obj = New Test1BO.ClassComPlus
Dim connectionString = ConfigurationManager.ConnectionStrings("DB1").ConnectionString
Dim cmdText = "WAITFOR DELAY '00:00:011'"
Try
MyTrace("TestDbExecNonQuery begin " & cmdText & ", 0, 0")
obj.DbExecuteNonQuery(connectionString, cmdText, 0, 0)
MyTrace("TestDbExecNonQuery end")
Catch ex As Exception
MyTrace("TestDbExecNonQuery exception")
MyTrace(ex.Message)
End Try
cmdText = "WAITFOR DELAY '00:00:05'"
Try
MyTrace("TestDbExecNonQuery begin " & cmdText & ", 6, 0")
obj.DbExecuteNonQuery(connectionString, cmdText, 6, 0)
MyTrace("TestDbExecNonQuery end")
Catch ex As Exception
MyTrace("TestDbExecNonQuery exception")
MyTrace(ex.Message)
End Try
Try
MyTrace("TestDbExecNonQuery begin " & cmdText & ", 0, 6")
obj.DbExecuteNonQuery(connectionString, cmdText, 0, 6)
MyTrace("TestDbExecNonQuery end")
Catch ex As Exception
MyTrace("TestDbExecNonQuery exception")
MyTrace(ex.Message)
End Try
obj.Dispose()
End Sub
Sub Main()
TestDbExecNonQuery()
Console.WriteLine("Press any key to close")
Console.ReadKey()
End Sub
End Module
При выполнении вышеизложенного ожидается следующий результат:
2016-12-09 16:47:17 TestDbExecNonQuery begin WAITFOR DELAY '00:00:011', 0, 0
2016-12-09 16:47:29 TestDbExecNonQuery exception
2016-12-09 16:47:29 The root transaction wanted to commit, but transaction aborted (Exception from HRESULT: 0x8004E002)
2016-12-09 16:47:29 TestDbExecNonQuery begin WAITFOR DELAY '00:00:05', 6, 0
2016-12-09 16:47:40 TestDbExecNonQuery end
2016-12-09 16:47:40 TestDbExecNonQuery begin WAITFOR DELAY '00:00:05', 0, 6
2016-12-09 16:47:51 TestDbExecNonQuery exception
2016-12-09 16:47:51 The root transaction wanted to commit, but transaction aborted (Exception from HRESULT: 0x8004E002)
Press any key to close