我们有一个Web服务接受来自外部网站的请求,然后使用Acumatica API根据请求添加或更新客户记录。当我们一次收到一个请求时,这工作正常。问题是外部站点批量处理他们的请求,然后同时发送多个请求。这意味着我们最终会同时运行两个或多个请求,这意味着一次发生多个登录和多个上下文。这最终几乎总是产生一个模糊的"对象引用未设置为对象的实例"在许多GetSchema()调用之一。我还看到了一些锁定违规错误,例如:"错误#147:另一个进程添加了' CSAnswers'记录。您的更改将会丢失。"
我在下面创建了一个测试用例,可以通过向发出所有API调用的同一个网页发送3个异步Web请求来复制此事件。另一个问题是,在我没有运行一段时间之后运行它时似乎总是会产生错误。如果我再次立即运行它,那么它通常会成功。这让我觉得可能会在后续调用中缓存某些内容,因此运行速度更快,然后不会自行运行???我不知道,我尝试添加一些延迟,看看是否会在后续运行中更频繁地发生,但它没有。
有人知道Acumatica API是否肯定不支持异步/同步上下文?我只看到了this post on automated scheduling where Gabriel mentioned some thread safety concerns,但不确定这是一回事。
代码是VB中的两个ASP.Net页面。 Default.aspx只是用于创建对CreateReservation.aspx的单个和同时调用的一些按钮。
我们正在使用Acumatica版本4.20.2063和IIS 8.5,我认为管道是.net 4.0集成的。谢谢!
Default.aspx.vb:
<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Default.aspx.vb" Inherits="AcumaticaTesting._Default" Async="true" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label runat="server" ID="lblMessage" ForeColor="Red"></asp:Label>
<br />
<asp:Button runat="server" ID="btnStartOne" Text="Run One" OnClick="btnStartOne_Click" />
<br />
<asp:Button runat="server" ID="btnStartAsynch" Text="Run Three (Asynchronous)" OnClick="btnStartAsynch_Click" />
</div>
</form>
</body>
</html>
Default.aspx.vb(相关方法)
Protected m_webRequest1 As WebClient
Protected m_webRequest2 As WebClient
Protected m_webRequest3 As WebClient
Protected m_webAddress As String = "http://localhost:61343/CreateReservation.aspx"
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Protected Sub btnStartOne_Click(sender As Object, e As EventArgs)
Dim uri As Uri = New Uri(m_webAddress)
m_webRequest1 = New WebClient()
AddHandler m_webRequest1.OpenReadCompleted, AddressOf OpenReadCallback
m_webRequest1.OpenReadAsync(uri)
End Sub
Protected Sub btnStartAsynch_Click(sender As Object, e As EventArgs)
Dim uri As Uri = New Uri(m_webAddress)
m_webRequest1 = New WebClient()
AddHandler m_webRequest1.OpenReadCompleted, AddressOf OpenReadCallback
m_webRequest1.OpenReadAsync(Uri)
Threading.Thread.Sleep(CInt(Int(50))) ' milliseconds
m_webRequest2 = New WebClient()
AddHandler m_webRequest2.OpenReadCompleted, AddressOf OpenReadCallback
m_webRequest2.OpenReadAsync(uri)
Threading.Thread.Sleep(CInt(Int(50))) ' milliseconds
m_webRequest3 = New WebClient()
AddHandler m_webRequest3.OpenReadCompleted, AddressOf OpenReadCallback
m_webRequest3.OpenReadAsync(uri)
End Sub
' THIS IS JUST A CALLBACK FOR THE ASYNCHRONOUS CALLS, ALL IT DOES IS SET A STATUS MESSAGE
Protected Sub OpenReadCallback(sender As Object, e As OpenReadCompletedEventArgs)
Dim reply As Stream = Nothing
Dim s As StreamReader = Nothing
Try
reply = CType(e.Result, Stream)
s = New StreamReader(reply)
Console.WriteLine(s.ReadToEnd())
Finally
If Not s Is Nothing Then
s.Close()
End If
If Not reply Is Nothing Then
reply.Close()
End If
End Try
lblMessage.Text = "Received result"
End Sub
CreateReservation.aspx(相关方法)
' HELPER METHOD
Protected Function CreateValue(screenField As AcumaticaAPI.Field, newVal As String) As Value
Return CreateValue(screenField, newVal, False)
End Function
' HELPER METHOD
Protected Function CreateValue(screenField As AcumaticaAPI.Field, newVal As String, addCommit As Boolean) As Value
Dim theValue As Value = New Value()
theValue.LinkedCommand = screenField
theValue.Value = newVal
If addCommit Then
theValue.Commit = True
End If
Return theValue
End Function
' PAGE_LOAD MAKES ALL THE ACTUAL API CALLS
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim delayAmt As Integer = 4000
' Initialize the random-number generator.
Randomize()
Dim nameEnd As Integer = Now.Millisecond
Dim fullName As String = "Doe, John" + nameEnd.ToString()
' STEP 1: Login
Dim context1 As AcumaticaAPI.Screen
context1 = New AcumaticaAPI.Screen
context1.CookieContainer = New System.Net.CookieContainer()
context1.AllowAutoRedirect = True
context1.EnableDecompression = True
context1.Timeout = 1000000
context1.Url = ACUMATICA_URL
Dim login1 As LoginResult = context1.Login(ACUMATICA_USER, ACUMATICA_PWD)
' STEP 2 : See if customer exists
Dim CR303000 As CR303000Content = context1.CR303000GetSchema()
context1.CR303000Clear()
Dim nameFilter As Filter = New Filter()
nameFilter.Field = CR303000.AccountSummary.BusinessAccountName
nameFilter.Condition = FilterCondition.Equals
nameFilter.Value = fullName
Dim searchfilters() As Filter = {nameFilter}
Dim searchCommands() As Command = {CR303000.AccountSummary.BusinessAccount, CR303000.DetailsMainContact.Phone1, CR303000.DetailsMainContact.Phone2}
Dim searchResult As String()() = context1.CR303000Export(searchCommands, searchfilters, 0, False, False)
' STEP 3 CREATE CUSTOMER
Dim AR303000 As AR303000Content = context1.AR303000GetSchema()
context1.AR303000Clear()
' create customer with just name for now
Dim nameVal As Value = CreateValue(AR303000.CustomerSummary.CustomerName, fullName)
' other fields required for Customer
Dim classVal As Value = CreateValue(AR303000.GeneralInfoFinancialSettings.CustomerClass, "DEFAULT")
Dim statementCycleVal As Value = CreateValue(AR303000.GeneralInfoFinancialSettings.StatementCycleID, "ENDOFMONTH")
Dim statementTypeVal As Value = CreateValue(AR303000.BillingSettingsPrintAndEmailSettings.StatementType, "Open Item")
Dim cashDiscountAccountVal As Value = CreateValue(AR303000.GLAccountsCashDiscountAccount.CashDiscountAccount, "10103")
Dim creditVerificationVal As Value = CreateValue(AR303000.GeneralInfoCreditVerificationRulesCreditVerification.CreditVerification, "Disabled")
' execute insert with just name and required fields
Dim insertCommands As Command() = {nameVal, classVal, statementCycleVal, statementTypeVal, cashDiscountAccountVal, creditVerificationVal, AR303000.Actions.Save}
Dim insertResult As AR303000Content() = context1.AR303000Submit(insertCommands)
' STEP 4 : Find the newly created Customer record
Dim CR303000_2 As CR303000Content = context1.CR303000GetSchema()
context1.CR303000Clear()
Dim nameFilter_2 As Filter = New Filter()
nameFilter_2.Field = CR303000_2.AccountSummary.BusinessAccountName
nameFilter_2.Condition = FilterCondition.Equals
nameFilter_2.Value = fullName
Dim searchfilters_2() As Filter = {nameFilter_2}
Dim searchCommands_2() As Command = {CR303000_2.AccountSummary.BusinessAccount, CR303000_2.DetailsMainContact.Phone1, CR303000_2.DetailsMainContact.Phone2}
Dim searchResult_2 As String()() = context1.CR303000Export(searchCommands_2, searchfilters_2, 0, False, False)
Dim newCustomerID As String = searchResult_2(0)(0)
' STEP 5 : Add Business Acct fields
Dim CR303000_3 As CR303000Content = context1.CR303000GetSchema()
context1.CR303000Clear()
' create key field
Dim baKeyVal As Value = CreateValue(CR303000_3.AccountSummary.BusinessAccount, newCustomerID.ToString())
Dim baClassIDVal As Value = CreateValue(CR303000_3.DetailsCRM.ClassID, "DEFAULT")
' create custom fields to update at same time
Dim passwordName As Value = CreateValue(CR303000_3.Attributes.Attribute, CUST_ATTRIBUTE_ID_PASSWORD)
Dim passwordVal As Value = CreateValue(CR303000_3.Attributes.Value, "-------", True)
Dim secretQuestionName As Value = CreateValue(CR303000_3.Attributes.Attribute, CUST_ATTRIBUTE_ID_SECRET_QUESTION)
Dim secretQuestionVal As Value = CreateValue(CR303000_3.Attributes.Value, "QQQQQQ", True)
Dim secretAnswerName As Value = CreateValue(CR303000_3.Attributes.Attribute, CUST_ATTRIBUTE_ID_SECRET_ANSWER)
Dim secretAnswerVal As Value = CreateValue(CR303000_3.Attributes.Value, "AAAAAAA", True)
' execute update
Dim updateBACommands As Command() = {baKeyVal, baClassIDVal, passwordName, passwordVal, secretQuestionName, secretQuestionVal, secretAnswerName, secretAnswerVal, CR303000_3.Actions.Save}
Dim updateBAResult As CR303000Content() = context1.CR303000Submit(updateBACommands)
End Sub
我一直得到的完整例外是这个,但它可以在不同的GetSchema()调用中发生:
System.Web.Services.Protocols.SoapException was unhandled by user code
Actor=""
HResult=-2146233087
Lang=""
Message=System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at PX.Api.ScreenUtils.GetScreenInfo(String screenId, Boolean appendDescriptors)
at PX.Api.ScreenUtils.GetScreenInfoWithServiceCommands(Boolean appendDescriptors, String screenID)
at PX.Api.Services.ScreenService.Get(String id, SchemaMode mode)
at PX.Api.Soap.Screen.ScreenGeneric.GetSchema(String screenID)
--- End of inner exception stack trace ---
Node=""
Role=""
Source=System.Web.Services
StackTrace:
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
at AcumaticaTesting.AcumaticaAPI.Screen.AR303000GetSchema() in C:\Users\Eric\Documents\Visual Studio 2013\Projects\AcumaticaTesting\AcumaticaTesting\Web References\AcumaticaAPI\Reference.vb:line 671
at AcumaticaTesting.CreateReservation.Page_Load(Object sender, EventArgs e) in C:\Users\Eric\Documents\Visual Studio 2013\Projects\AcumaticaTesting\AcumaticaTesting\CreateReservation.aspx.vb:line 59
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
InnerException:
答案 0 :(得分:2)
这是已知的错误,已在5.20.1227版中修复。
原因是系统构建屏幕架构时会发生竞争情况。此模式仅在应用程序池被回收时构建,这就是您在系统空闲一段时间后遇到的原因。手动回收应用程序池应该强制行为发生。