Lesson 3: Implementing an Asynchronous Operation
An operation that is expected to take a long time to complete should be executed asynchronously: that is, executed on a separate thread so that it does not block the main thread of an application and freeze up the user interface. In Acumatica ERP, you should execute asynchronous operations by using the PXLongOperation class. In this lesson, you will learn how to process operations whose execution takes a long time.
Asynchronous Operations: General Information
An instance of a graph is created on each round trip to process a request created by the user on the appropriate form. Aer the request is processed, the graph instance must be cleared from the memory of the Acumatica ERP server. If you implement code that might require a long time to execute an action or to process a document or data, you should execute this code asynchronously in a separate thread. In this lesson, you will learn how to run an operation asynchronously by using the PXLongOperation class.
Learning Objectives
In this lesson, you will learn how to do the following:
- Implement a long-running action
- Create the associated button on the table toolbar for the long-running action
- Set up the long-running action to execute asynchronously by using the PXLongOperation class
Applicable Scenarios
You set up an operation to run asynchronously when this operation is expected to take a long time to finish its execution.
Use of the PXLongOperation Class
To make the system invoke the method in a separate thread, you can use the PXLongOperation.StartOperation method. Within the method that you pass to StartOperation, you can, for example, create a new instance of a graph and invoke a processing method on that instance. The following code snippet demonstrates how you can execute code asynchronously as a long-running operation in a method of a graph.
To instantiate graphs from code, use the PXGraph.CreateInstance<T>() method. Do not use
the new T() graph constructor because in this case, no extensions or overrides of the graph are
initialized.
public class MyGraph : PXGraph
{
...
public void MyMethod()
{
...
PXLongOperation.StartOperation(this, delegate()
{
// insert the delegate method code here
...
Lesson 3: Implementing an Asynchronous Operation | 24
GraphName graph = PXGraph.CreateInstance<GraphName>();
foreach (... in ...)
{
...
}
...
});
...
}
...
}
Do not pass a reference to this graph to the StartOperation method from a graph extension.
Otherwise, the system cannot notify the UI about whether any errors occurred during the
asynchronous operation or whether the operation was completed.
If you need to save data to the database inside a long-running operation, call the Save.Press() method of the current graph. We do not recommend that you use the Actions.PressSave() method because it performs an external call and should be used from the UI only. The following code shows an example of a method called InvoiceOrder that is to be executed asynchronously. This method is being called within the delegate that you pass to PXLongOperation.StartOperation() method.
PXLongOperation.StartOperation(this, delegate ()
{
InvoiceOrder(graphCopy);
});
The PXLongOperation.StartOperation() method creates a separate thread and executes the specified delegate asynchronously on this thread. The method passed into PXLongOperation.StartOperation() matches the following delegate type, which has no input parameters.
delegate void PXToggleAsyncDelegate();
The anonymous method definition (delegate()) is used to shorten the code in the example.
Inside the delegate() method, you should not use members of the current graph, because this would lead to synchronous execution of the method. Instead, use a copy of the graph, which you can create by using the var graphCopy = this.Clone(); statement. Related Links
- PXLongOperation
- Asynchronous Operations: How They are Handled in Acumatica ERP
Activity 3.1: To Implement an Asynchronous Operation
The following activity will walk you through the process of implementing a long-running action and executing it asynchronously. Lesson 3: Implementing an Asynchronous Operation | 25
Story
Suppose that you need to create an action that allows users to validate the prices for the repair items added in the table on the Repair Items tab of the Services and Prices (RS203000) form, in the PhoneRepairShop customization project. Suppose that you need to validate the prices by using an external service. This action can potentially take a long time to finish its execution and hence it should be executed asynchronously. You need to define an action in the graph of the form and configure the associated button (on the table toolbar). You need to define the code that will be used to perform the validation of the prices and execute this code asynchronously by using the PXLongOperation.StartOperation method.
Process Overview
In this activity, you will create an action on the table toolbar and execute it asynchronously by performing the following steps:
- Adding the IsPriceValidated field, which you will add to the database during system preparation, to the RSSVRepairItem DAC, and updating the ASPX file of the Services and Prices (RS203000) form. This will make this field visible as the Price Validated column, in the table on the Repair Items tab of the form.
- Defining the logic needed to validate the prices of the repair items in a method called ValidatePrices, implementing the ValidateItemPrices action and setting it up the to run the ValidatePrices method asynchronously, and creating the associated Validate Prices button on the table toolbar.
- Testing the Validate Prices button and the underlying action.
System Preparation
Before you begin performing the steps of this activity, do the following:
- In SQL Server Management Studio, execute the T230_AddColumn_RSSVRepairItem.sql script.
The script is provided in the Customization\T230\SourceFiles\DBScripts folder, which you have downloaded from Acumatica GitHub. - To update the customization project, do the following: a. In the Customization Project Editor, open the PhoneRepairShop project. b. In the navigation pane, click Database Scripts. c. On the More menu of the Database Scripts page, which opens, click Reload from Database.
Step 1: Updating the Services and Prices (RS203000) form to Display the Price Validated Column and the Validate Prices Button To add the IsPriceValidated field to the RSSVRepairItem DAC and update the corresponding ASPX file to respectively show the Price Validated column in the table, and the Validate Prices button on the table toolbar of the Repair Items tab, do the following:
- Add the following code to the RSSVRepairItem.cs file aer the BasePrice field definition.
#region IsPriceValidated [PXDBBool] [PXDefault(false)] [PXUIField(DisplayName = "Price Validated", Enabled = false)] public virtual bool? IsPriceValidated { get; set; } public abstract class isPriceValidated :
Lesson 3: Implementing an Asynchronous Operation | 26
PX.Data.BQL.BqlBool.Field<isPriceValidated> { }
#endregion
Note that the field is set to be disabled on the UI because this field is meant to be updated by an external
service. The field is set to false by default.
2. In the RS203000.aspx file, do the following:
a. Add the following code inside the
<px:PXGridColumn Type="CheckBox" DataField="IsPriceValidated" Width="100" >
</px:PXGridColumn>
This code has added the Price Validated column, which is represented by the IsPriceValidated
DAC field, to the table on the Repair Items tab.
b. Add the following code in the <px:PXTabItem Text="Repair Items"> tag aer the Levels
closing tag.
<ActionBar>
<CustomItems>
<px:PXToolBarButton Text="ValidateItemPrices">
<AutoCallBack Command="ValidateItemPrices" Target="ds" />
</px:PXToolBarButton>
</CustomItems>
</ActionBar>
In this code, you have defined an action bar on the table toolbar of the Repair Items tab and have added
the Validate Prices button to it.
3. Save your changes.
Step 2: Defining the Logic Used to Validate Prices You should define the method in which the repair items prices are validated, and then you can call this method in the PXLongOperation.StartOperation method. Since the objective of this activity is to execute a long-running operation asynchronously by using the PXLongOperation.StartOperation method, you will not be focusing on the specifics of the logic of the long-running operation itself —that is, you will not be writing the code for connecting to an actual external service, making a request and parsing the received result. Instead, you will use the Thread.Sleep() method to create a delay in the execution of the method to simulate connecting to an external service. You will simply pass the repair items to the ValidatePrices method and set the IsPriceValidated field for each repair item to true. This will change the state of the check box in the Price Validated column of the Repair Items tab to be selected for each repair item and simulate a successful validation from an external service. To define the method in which the repair items prices are validated, do the following:
- Add the following using directives to the RSSVRepairPriceMaint.cs file (if they have not been
added yet).
using System.Collections; using System.Collections.Generic; using System.Threading;
Lesson 3: Implementing an Asynchronous Operation | 27
Instead of adding the using directives manually, you can add them with the help of the
Quick Actions and Refactorings feature of Visual Studio aer you define the method in the next
instruction.
2. Add the following static method, ValidatePrices, to the RSSVRepairPriceMaint graph. The ValidatePrices method validates the repair items prices for the selected record on the Services and Prices (RS203000) form.
private static void ValidatePrices(RSSVRepairPrice repairPriceItem)
{
// Create an instance of the RSSVRepairPriceMaint graph
// and set the Current property of its RepairPrices view.
var priceMaint = PXGraph.CreateInstance<RSSVRepairPriceMaint>();
priceMaint.RepairPrices.Current = priceMaint.RepairPrices.
Search<RSSVRepairPrice.serviceID, RSSVRepairPrice.deviceID>
(repairPriceItem.ServiceID, repairPriceItem.DeviceID);
// Set a delay to mimic connecting to an external service to validate the
// repair item prices.
// In a real world scenario, you would connect to an actual external
// service and make an API request to validate the prices for
// the repair items.
Thread.Sleep(3000);
// Update the Price Validated field for each repair item on
// the Repair Items tab:
// Here we are assuming that the validation was successful from the
// external service and are setting IsPriceValidated to true for
// each repair item.
foreach (RSSVRepairItem item in priceMaint.RepairItems.Select())
{
// Set IsPriceValidated to true for each repair item.
item.IsPriceValidated = true;
// Update the cache with the above change for each repair item.
priceMaint.RepairItems.Update(item);
}
// Trigger the Save action to save the changes stored in the cache
// to the database.
priceMaint.Actions.PressSave();
}
In the method above, you first create an instance of the RSSVRepairPriceMaint graph. You then set the current property of the RepairPrices view of this graph instance to the parameter of type RSSVRepairPrice that was passed into the ValidatePrices method. You then use the Thread.Sleep(3000) method call to pause the execution of the method to simulate a long- running operation that is connecting to an external service. You then loop through each repair item of the selected record on the Services and Prices form and set its IsPriceValidated property to true to indicate that its price has been validated. Finally, you update the cache and save the changes to the database.
Step 3: Defining the ValidateItemPrices Action The ValidateItemPrices action defines the underlying action for the Validate Prices button on the table toolbar of the Repair Items tab, and invokes the PXLongOperation.StartOperation method that executes the ValidatePrices method added in Step 2, asynchronously. Lesson 3: Implementing an Asynchronous Operation | 28
To define the ValidateItemPrices action, add the following code to the RSSVRepairPriceMaint graph.
#region Actions
public PXAction<RSSVRepairPrice> ValidateItemPrices = null!;
[PXButton(DisplayOnMainToolbar = false, CommitChanges = true)]
[PXUIField(DisplayName = "Validate Prices", Enabled = true)]
protected virtual IEnumerable validateItemPrices(PXAdapter adapter)
{
// Populate a local list variable.
List<RSSVRepairPrice> list = new List<RSSVRepairPrice>();
foreach (RSSVRepairPrice repairItemPrice in adapter.Get<RSSVRepairPrice>())
{
list.Add(repairItemPrice);
}
// Trigger the Save action to save changes in the database.
Actions.PressSave();
var repairPriceItem = RepairPrices.Current;
// Execute the ValidatePrices method asynchronously by
// using PXLongOperation.StartOperation
PXLongOperation.StartOperation(this, () => ValidatePrices(repairPriceItem));
// Return the local list variable.
return list;
}
#endregion
To perform a background operation, an action method needs to have a parameter of the PXAdapter
type and return IEnumerable.
In the ValidateItemPrices method, you compose a list of services and prices by using the adapter.Get method, and invoke the Actions.PressSave action. Because the return of the adapter.Get method does not include data that has not been saved on the form, by calling the PressSave method, you update the records in the composed list. Then you use the PXLongOperation.StartOperation() method to validate the prices of the repair items on the Repair Items tab for the record that is selected on the Services and Prices (RS203000) form. You do this by invoking the ValidatePrices method within the method that you pass to StartOperation(). Finally, you return the list of services and prices.
Step 4: Testing the Validate Prices Button and the Associated Action To test the Validate Prices button and the underlying action, do the following:
- Rebuild the PhoneRepairShop_Code project in Visual Studio, and publish the customization project in the Customization Project Editor.
- In Acumatica ERP, open the Services and Prices (RS203000) form.
- Open the BATTERYREPLACE service. Notice that the check box in the Price Validated column is not selected for any repair item on the Repair Items tab and that the Validate Prices button is available on the table toolbar.
- On the table toolbar, click Validate Prices. A notification appears indicating the status of the processing, as shown in the following screenshot. Lesson 3: Implementing an Asynchronous Operation | 29
Figure: Validation of the prices of repair items
When the process is complete, the check box for each repair item is selected in the Price Validated column of the Repair Items tab, as shown in the following screenshot.
Figure: Update of the Price Validated column Lesson 3: Implementing an Asynchronous Operation | 30
Lesson Summary
In this lesson, you have learned how to initiate an asynchronous operation inside an action method by using the PXLongOperation class, by doing the following:
- Defining the static ValidatePrices method, which simulates a long-running operation, in the RSSVRepairPriceMaint graph
- Defining the Validate Prices button on the table toolbar of the Services and Prices (RS203000) form; the underlying action initiates the asynchronous execution of the ValidatePrices method by using the PXLongOperation class The following diagram shows the changes that you have made in this lesson. Appendix: Initial Configuration | 31
Appendix: Initial Configuration If for some reason, you cannot complete the instructions in Initial Configuration, you can create an Acumatica ERP instance and manually publish the needed customization project, as described in this topic.
Step 1: Deploying the Needed Acumatica ERP Instance for the Training Course You deploy an Acumatica ERP instance and configure it as follows:
- To deploy a new application instance, open the Acumatica ERP Configuration wizard, and do the following: a. On the Database Configuration page, type the name of the database: SmartFix_T230. b. On the Tenant Setup page, set up a tenant with the T100 data inserted by specifying the following settings:
- Tenant Name: MyTenant
- New: Selected
- Insert Data: T100
- Parent Tenant ID: 1
- Visible: Selected c. On the Instance Configuration page, in the Local Path of the Instance box, select a folder that is outside of the C:\Program Files (x86), C:\Program Files, and C:\Users folder. We recommend that you store the website folder outside of these folders to avoid an issue with permission to work in these folders when you perform customization of the website. The system creates a new Acumatica ERP instance, adds a new tenant, and loads the selected data to it.
- Sign in to the new tenant by using the following credentials:
- Username: admin
- Password: setup Change the password when the system prompts you to do so.
- In the top right corner of the Acumatica ERP screen, click the username, and then click My Profile. The User Profile (SM203010) form opens. On the General Info tab, select YOGIFON in the Default Branch box; then click Save on the form toolbar. In subsequent sign-ins to this account, you will be signed in to this branch.
- Optional: Add the Customization Projects (SM204505) and Generic Inquiry (SM208000) forms to your favorites. For details about how to add a form to your favorites, see Favorites: General Information.
Step 2: Publishing the Required Customization Project Load the customization project with the results of the T220 Data Entry and Setup Forms training course and publish this project as follows:
- On the Customization Projects (SM204505) form, create a project with the name PhoneRepairShop, and open it.
- In the menu of the Customization Project Editor, click Source Control > Open Project from Folder.
- In the dialog box that opens, specify the path to the Customization\T220\PhoneRepairShop folder, which you have downloaded from Acumatica GitHub, and click OK.
- Bind the customization project to the source code of the extension library as follows:
a. Copy the Customization\T220\PhoneRepairShop_Code folder to the App_Data\Projects
folder of the website.
Appendix: Initial Configuration | 32
By default, the system uses the App_Data\Projects folder of the website as the parent folder for the solution projects of extension libraries. If the website folder is outside of the C:\Program Files (x86), C:\Program Files, and C:\Users folders, we recommend that you use the App_Data\Projects folder for the project of the extension library. If the website folder is in the C:\Program Files (x86), C:\Program Files, or C: \Users folder, we recommend that you store the project outside of these folders to avoid an issue with permission to work in these folders. In this case, you need to update the links to the website and library references in the project. b. Open the solution, and build the PhoneRepairShop_Code project. c. Reload the Customization Project Editor. d. In the menu of the Customization Project Editor, click Extension Library > Bind to Existing. e. In the dialog box that opens, specify the path to the App_Data\Projects \PhoneRepairShop_Code folder, and click OK. - On the menu of the Customization Project Editor, click Publish > Publish Current Project.
The Modified Files Detected dialog box opens before publication because you have rebuilt the extension library in the PhoneRepairShop_Code Visual Studio project. The Bin \PhoneRepairShop_Code.dll file has been modified and you need to update it in the project before the publication.
The published customization project contains all changes to the Acumatica ERP website and database that have been performed in the T200 Maintenance Forms, T210 Customized Forms and Master-Detail Relationship, and T220 Data Entry and Setup Forms training courses. This project also contains the customization plug-ins that fill in the tables created in the T200 Maintenance Forms, T210 Customized Forms and Master-Detail Relationship, and T220 Data Entry and Setup Forms training courses with the custom data entered in these training courses. For details about the customization plug-ins, see To Add a Customization Plug-In to a Project. (The creation of customization plug-ins is outside of the scope of this course.)