Challenges of Asynchronous Programming:
From the AJAX Interface to the Database – A Case Study
Robert Dollinger
Mathematics and Computing Department, University of Wisconsin Stevens Point,
Stevens Point, Wisconsin, USA,
rdolling@uwsp.edu
Joseph Barjis
Mathematics and Computing Department, University of Wisconsin Stevens Point,
Stevens Point, Wisconsin, USA,
jbarjis@uwsp.edu
Abstract: In developing educational multimedia applications, asynchronous programming is becoming more common in application development due to recent advances in technologies like AJAX and asynchronous database connectivity. The software community seems to be unprepared to handle the challenges of developing, testing and debugging asynchronous applications. There is a need for tools, techniques and models to properly address this situation. We present a case study that illustrates the use of AJAX and asynchronous database connections. Developing this classroom demo proved to be more challenging than anticipated, and revealed several weaknesses of the traditional application development approach. Hidden errors in the functionality of the asynchronous callbacks version of the application could be detected only by using modeling based on formal semantics (e.g., Petri nets) amenable to automatic analysis (simulation). The model revealed the complexity of the interactions between several asynchronous threads, and allowed us to detect a potential live-lock situation with the application.
1. Introduction
Recent developments in software technology, like AJAX (Asynchronous JavaScript and XML) (Gibbs, 2007; McClure, 2006; Moore, 2007; Pars, 2007; Woolston, 2006; Zakas, 2006) and asynchronous database connections (Evjen et all, 2006), are changing the way programmers create their applications.
In AJAX based Web interfaces the ability to send asynchronous requests to the Web server is the key to more responsive and flexible applications. The user does not have to wait anymore for the system’s reaction to a button click, but can continue to work while one or more of his requests are processed by the Web Server. This is a clear break from the rigid mold of the traditional Web applications where the user has to wait for the completion of the server round-trip while contemplating a frozen and annoyingly non-responsive Web page.
On the database side, the ability to issue data requests through asynchronous connections allows applications to truly benefit of the databases’ capabilities of concurrent and asynchronous processing. The same application can now send several asynchronous queries, one after the other, through different database connections, to one or more database servers. Such queries will be processed in true parallel mode, thus drastically reducing response times. For example, in a WebShopping application the process retrieving the information of a preferred customer, and the discounts he may benefit of, would run in parallel with the process performing the transactions associated to his current purchases (like adding items to the shopping cart and adjusting store stocks). When both processes finish, a third process may apply the discounts to the products in the shopping cart.
It is obvious that asynchronous programming has some unquestionable advantages when it comes to develop faster and suppler applications, and it will gain more and more ground in software development. Unfortunately, asynchronous programming brings with it challenges that the current methodologies, techniques and tools fail to properly address. These challenges are present in every phase of the software development cycle: analysis, design, implementation, testing and debugging.
In this paper, we present a case study, built on a simple classroom demo application that was initially meant only to illustrate the advantages of asynchronous programming. The demo was prepared for the first AJAX course ever thought at University of Wisconsin Stevens Point. It proved to be a more challenging, but at the same time generous, experiment than initially anticipated. It showed us and our students how much one can learn from a relatively simple classroom demo application, and how profound the implications of it can be.
The further layout of this paper is as follows: Sections 2 and 3 introduce the basic concepts of the AJAX technology, and the asynchronous database connections. Section 4 briefly explains the demo application. Section 5 addresses some issues related to the architectural design of the application. The challenges and pitfalls in the implementation of the asynchronous callbacks version of the application are presented in section 6. A simple solution that seemed to address these problems is also presented. In section 7 Petri nets based models are identified as a tool to validate the correctness and functionality of the asynchronous applications. Section 8, illustrates how simulations performed on the Petri net model of our demo application revealed hidden weaknesses of our proposed solution. Namely, the potential for a non-trivial live-lock situation and/or possibility of data loses could be detected, and a possible remedy is proposed. Section 9, provides some thoughts and conclusions relative to the need for new tools and techniques when developing asynchronous applications.
2. The AJAX Technology
AJAX is a set of technologies, or rather a methodology, that changes the way people perceive Web applications. As Web applications become richer, both in information content and by their interactive features, AJAX is becoming the de facto standard in Web development (Pars, 2007). Many people claim that we are on the verge of another programming revolution, a revolution that will free the computer users from the constraints of desktop applications and from the dependence on a specific software provider (McClure, 2006). Indeed AJAX based Web applications look and feel more and more like Windows-based rich client applications, while still having all the advantages Web applications have over windows based application, i.e. easy deployment and platform independence. This is true both from the end user point of view, and from the perspective of the application developer. The end user experiences an information rich, flexible and responsive interface free of the annoying latency problems associated with postbacks. On the other hand, developers are largely relieved from the painful issues of state management. With AJAX one can fire requests for data from the Web server without changing the current page in the browser. This approach clearly reminds of the code patterns used in Windows forms applications.
AJAX combines several technologies to leverage the browser capabilities in order to reduce the amount and frequency of server postbacks. With AJAX, most of the regular postback cycles are eliminated or replaced by requests for some specific data, which result in partial updates of the Web page. This considerably reduces the load on the Web Server and enhances scalability of the server side resources. However, what makes interactive Web applications more responsive is the asynchronous nature of the AJAX requests. This is a key feature that breaks out of the synchronous request/response model and enables the end users to continue their work without interruption while the server returns its response.
3. Asynchronous Database Requests
Most database engines are well equipped to support multiple simultaneous client requests. The problems of concurrency control and transaction processing have been extensively studied and are fairly well understood by now (Gray, 1993). While concurrent processing allowed requests from different clients to be processed in parallel by the database, requests coming from one single user were still served sequentially. Recent features added to development systems, like VS.NET 2005, provide support for asynchronous command execution and asynchronous connections (Evjen et all, 2006). Asynchronous commands allow an application to fire several requests one after the other without having to wait for the previous one to be processed by the database. If each request is directed towards a different database server then all request will be processed in parallel, resulting in reduced response times. If the requests are directed to one single database the requests are still processed in sequence. However, when the application fires each request through a separate asynchronous connection then the requests are processed concurrently by the database engine, which in the case of independent queries, may result in a quasi-parallel processing.
4. The Case Study
The case study we present is based on a simple classroom demo we called the Supplier-Consumer Transactions (SCT) application. This is an AJAX enabled Web application that consists of a single Web page. Several versions of the application have been implemented in order to illustrate various features and options of the AJAX technology and of the asynchronous database requests feature. All versions provide the same unique user interface and functionality as illustrated in Figure 1.
T he SCT application selectively extracts and displays on the Web page data from three tables of a database: Tb_Supplier, Tb_Consumer – that contain a list of suppliers and consumers, as well as Tb_Transactions – that stores the transactions between the suppliers and consumers in the previous two tables. These tables represent a section of a larder database the details of which are irrelevant for our presentation here.
The three tables are described by the relational schemas bellow:
Tb_Supplier(Supp_ID, Name, City)
Tb_Consumer(Con_ID, Name, City)
Tb_Transactions(Tran_ID, Supp_ID, Con_ID, Prod_ID, Quantity, Price)
In figure 1, the two selection lists on the upper-left side of the page allow the user to select a supplier and/or a consumer city. Based on this selection the SCT application extracts the corresponding data from the three tables and displays it in three dynamically created data grids. That is the first two grids will display the suppliers and the consumers from the respectively selected cities, while the third grid will display the transactions data between the suppliers and consumers that have been selected. A new selection will result in a new request for data that will replace the data already existing on the page. Note that no postback operation is involved in the process, and the Web page is not rendered again, only the data grids and the data within are seamlessly replaced, as the Web browser uses asynchronous Web server requests and the Web server uses asynchronous database requests to retrieve the data.
The SCT application has been developed by using ASP.NET 2.0 and an MSSQL 2005 database.
5. Architectural Options
Given the context described above, a developer is inherently challenged with a couple of questions and design decisions to be made:
(1) How is the data from the three tables traveling between the Web server and the client browser?
(a) table-by-table or (b) as one single package,
(2) How many database connections should be used to extract the data from the database?
(a) one single connection or (b) a separate connection for each and every table,
(3) Which asynchronous request method to use?
(a) the poll method, (b) the wait method or the (c) callback method.
Although the answers to some of the questions above may seem trivial, by implementing a separate version of the SCT application for each of the available options proved to be a useful experience and provided valuable classroom material for the analysis of performance, scalability and implementation issues of asynchronous applications.
The trade-offs for each of the options are briefly described bellows:
Option 1a:
The Web browser issues a separate asynchronous AJAX request for each table. There is some extra overhead associated with this option when compared with Option 1b, due to the two additional AJAX requests. However, this approach has the advantage that serialization of a simple data table is easily supported by most AJAX libraries, and developers also have the option of sending pre-formatted HTML representing the data grid for each table data directly from the Web server. In this case the requests further to the database will go through three different connections from three different and independent processes on the Web server, and will be serviced concurrently by the database. Whether asynchronous database connections are used or not is irrelevant with this option.
Option 1b:
Only one single AJAX request is used to bring the data as a DataSet from the Web Server. This option is a good example to illustrate how DataSet serializability is supported through JSON (JavaScript Object Notation) serializers. Such a feature is supported by libraries like Ajax.NET Professional (McClure, 2006) or Microsoft’s ASP.NET AJAX (Pars, 2007). In this case the Web server will issue three database requests to fill the three DataTable objects of a DataSet that is then sent back to the browser.
When choosing Option 1b above for transferring data between the Web browser and the Web server the following options are available for the next two decisions:
Option 2a:
With one single connection opened between the Web server and the database the only way to have several asynchronous requests issued at the same time is to use the MARS (Multiple Active Record Sets) option available in ADO.NET 2.0. With this option some overhead in the overall processing is saved, but the data requests are served sequentially by the database. Adding a measurable delay to each of the database queries would reveal the fact that the overall processing time is roughly the sum of the processing times of the three queries.
Option 2b:
The data from each of three tables is retrieved through a separate asynchronous database connection. In this case the requests are processed concurrently by the database, in a quasi-parallel manner. The MARS option is not needed in this case, and the total processing time is given by the longest query.
Both of the options above assume using one or more database connections that have to be asynchronous. No matter how many connections are used, there are, essentially, three options available in order to handle asynchronous database requests (Evjen et all, 2006):
Option 3a:
With the poll method the three database requests are fired by using one or more asynchronous connections, and then the status of each request is programmatically checked in a loop until all requests are served.
Option 3b:
The wait approach provides higher level of flexibility and provides good support for more complex scenarios with multiple asynchronous processes that may be dependent on each other. Each asynchronous request will be associated with a wait handler to synchronize it with the other processes. The wait handlers provide methods like WaitAll(), WaitAny() and other, that allow for the development of elaborate synchronization scenarios. In our application three independent asynchronous requests are fired and their associated wait handlers are placed in an array. Then the main process just iterates over this array until all requests are served. In each iteration the WaitAny() method is used to pick the next request that has the data available.
Option 3c:
The callback method is the most flexible and most efficient of all, since it eliminates all idle polling loops and wait times. All the asynchronous requests along with the caller process can run in parallel without having to wait for each other. When firing an asynchronous request a callback delegate pointing to the associated callback function is passed to it as parameter. When an asynchronous process finishes its work it calls the associated callback function to pick up the available results. Notice that the caller process only issues the asynchronous requests and has no responsibilities regarding the processing of the results from the asynchronous processes. This is the task of the callback function(s). In our application the main process issues three asynchronous requests passing as parameter the callback delegate of the same callback function for all three requests. The callback function has the role to place the retrieved data in the corresponding data table of the dataset to be returned to the browser. Notice that both in the poll and wait approach this was the responsibility of the caller process. The flexibility and efficiency of the callback method comes at a price. This approach is also the most problematic from an implementation point of view and posses several challenges to the developers.
6. Problems with the Asynchronous Callbacks
With the poll and wait approaches the caller function is still kept busy to poll for the results or is held by one of the wait functions until the results are returned. The callback approach is the only one that allows the caller function to continue execution and do something useful even after the asynchronous calls have been issued. The only concern of the caller process is to pass a reference to the callback function that will take care of the results returned by the asynchronous request. After this step, the main process and the asynchronous requests have a completely independent life. One key aspect here is also the fact that the callback functions are executed as independent threads.
In addition to this, in the context of our application, we have a scenario with three asynchronous calls that will call back to three independent instances of the same callback function.
Two problems need to be solved in the application in order to deal with these facts:
1) Pass to the callback function the context it needs
Typically, threads share their data, but in a scenario with multiple asynchronous calls and multiple instances of the callback function that are activated in an unpredictable order, passing some context from the caller to the callback function is essential. In our case the callback function is supposed to fill a DataTable with the data returned by a command object. The same callback function is called back by each of the three asynchronous operations which refer to three different DataTable objects and use three different commands. This means that the context of the calls needs to be passed to the callback function for correct operation. This context consists of a DataTable and a command object, which are packed as members of a custom class.
2) Synchronize the Web page thread with the callback functions
From the moment the asynchronous calls are issued, the caller application thread and the database requests are executed in parallel independently from each other. The situation is the same later when the callback functions get executed. Under these circumstances it is impossible to predict the status of the caller application thread by the time the callback functions would try to fill the DataTable objects with the data returned from the database. It is very possible that, by the time the callback functions collect the results from the database, the caller Web page processing cycle may have terminated its execution and returned with an Http Response to the browser without any, or only with some, of the data requested from the database. That is, all or some of the data may be returned from the database too late, after the caller terminates its execution cycle.
Moreover, once the caller application terminated, the data structures defined in its scope are gone, which means the callback functions are attempting to fill with data some DataTable objects that no longer exist. This, typically, produces a null pointer run time exception.
Thirdly, the caller process should not close the database connection(s) while the callback functions are still using that/those connection(s) to retrieve the data.
Clearly, correct termination of the caller process is dependent on the successful termination of all three callbacks.
A simple solution to this problem is to use a synchronization variable that would keep the caller from finishing execution until after all the callbacks terminate. The flag would be set to 0 before the asynchronous calls, and incremented by each callback. The caller will be allowed to terminate execution only after all three callbacks confirmed their completion. This test has to make sure that all callbacks terminate before closing the database connection, and before exiting the caller.
In a first attempt to address these issues we defined a class, called MailMan, whose members consist of the context data that needs to be transferred to the callback functions. One instance of this class will be created for each and every callback request. In addition, the MailMan class also defines a flag data member which is used for synchronization. This flag is defined as a static data member so that it acts as a shared variable among the caller application and the three callback functions.
The definition of the MailMan class is as follows:
public class MailMan
{
public SqlCommand theCommand;
public DataTable theTable;
public static int flag;
public MailMan(DataTable theTable)
{
this.theTable = theTable;
}
}
The MailMan.flag variable is set to zero by the caller application and then, later, incremented by each callback:
//one done...
MailMan.flag++;
The same flag is tested at the end of the caller function:
//BEFORE closing the connection and
//BEFORE returning from here...
//wait for the callbacks to finish!!!
while (MailMan.flag < 3) System.Threading.Thread.Sleep(10);
ajaxData.AcceptChanges();
sqlcn.Close();
The above solution seems to solve the synchronization problem and will likely pass tests involving single users of the application. Although, for the sake of simplicity, the flag variable is a static member of the MailMan class, we will see that this is causing some non-trivial problems, resulting in the failure of the application under some specific circumstances. These circumstances are not easy to reproduce, but are likely to happen altogether, which makes the problem even harder to identify and fix.
In the next two sections we show how by using a Petri net model of our application and by performing a simulation on this model one can reveal hidden flaws in the functionality of the application, flaws that are hard to detect, but may have serious consequences like producing a live-lock situation in the system running the application or causing loss of data..
7. Modeling and Simulation of the Asynchronous Callbacks Version
Due to their dynamic complexity, asynchronous applications become hard if not impossible to model, develop and debug without tools capable of dealing with the complex interactions within such applications. Unfortunately, such tools have yet to be developed, as the current development tools simply fail to address the problems of asynchronous applications. For our application we used a Petri net model and simulation to capture the hidden interactions between the various callback function instances.
Modeling plays an important role in software application development and it is obvious that the importance of models will increase as these systems grow in complexity. Today many of these complex software systems are even driven by models – model driven application development. For complex software applications such as asynchronous applications to truly benefit from a “Model Driven Architecture”, it is vital they start using models as a starting point, rather than using models as a means to document processes afterwards (Aalst, 2007).
While modeling is used to capture certain behaviors, the representation remains merely static, thus restricting the pragmatic quality of modeling. The best models become transparent to their users, i.e. the users are able to see through the model, so that when they manipulate the model, they appear to be interacting directly with the application. Static models are of limited pragmatic value compared to simulation and animation models. System behavior, response to events, and impact of changes are better studied using simulation. Students, users, and managers better understand if a process is animated (visual entities are used to illustrate how the process flows or components interact with each other).
For a thorough analysis and study of applications based on asynchronous interactions, both modeling and simulation should play in concert. Modeling by itself may not reveal sufficient information about the complex interactions within an application. For results with certain accuracy, modeling should be complemented with simulation. On the other hand, despite the abundance of powerful simulation tools, simulation alone may provide little help without profound conceptual modeling preceding it. It would be like “expedition without a map”. A valuable lesson extracted from the practice of modeling and simulation suggests that simulation without a profound concept (conceptual model) is possible, but it would be very hard, if not impossible, to achieve accurate and precise results.
Therefore, the modeling tools, used in analyzing asynchronous applications, should lend to subsequent simulation. This approach requires that the model should be based on some kind of formal semantics. For this purpose, Petri net has been widely used to study applications that have interactive, sequential, parallel, and concurrent nature. These are the features that many of asynchronous applications adhere to, and, therefore, we have used Petri nets as a tool for modeling and simulation, which is applied in the following section.
Assuming that readers are familiar with the basic concepts of Petri nets that are widely used in application development, we skip their introduction. Readers interested in Petri nets are referred to (e.g., Peterson, 1981; Reisig, 1985; Murata, 1989).
In general, as depicted in Figure 2, Petri net notations consists of places (graphically illustrated by circles and representing outcome of an activity or process), transitions (graphically illustrated by rectangles and representing an activity or process), and directed arcs (graphically illustrated by arrows and representing flow sequence).
8. Detecting Hidden Weaknesses/Flaws
In this section we discuss and illustrate how a Petri net simulation tool may help in learning the dynamic behavior of an asynchronous application. Before diving into this discussion, it should be noted that the Petri net used in this paper is of place-transition type (simple Petri net). We used this type of Petri net for two reasons. First, we wanted to demonstrate to the students a step by step animated process reflecting the interactions in our application. Second, simple Petri nets are easy to understand and will not require additional learning from students, while sufficiently expressive for the purposes of our simulation. Due to its intuitive nature, it was easy for our students to learn and understand how our Petri net model is constructed and executed.
In Figure 3, emphasis is made on one particular aspect, the parallel execution of three transactions (processes) based on a single entry request. The three transactions, executed in parallel represent the three callback functions, which execute independently and in parallel with each-other and the calling process to which they converge back and synchronize at the end of their execution. Figure 3 represents a screenshot of the Petri net simulation (token animation) in progress.
The simulation was intended to illustrate what was happening when several AJAX user requests where issued in sequence, and to explore what may happen if a user request comes to the Web server before processing of the previous request finishes. This may happen if several concurrent users run the Web application at the same time, a very likely scenario, or if the same user issues successive requests without waiting for the results of the previous ones. This latest scenario is possible in AJAX applications where users can continue their interaction with the application immediately after an asynchronous AJAX request without having to wait for the previous one to complete.
T he initial place in the Petri net model was initialized with 10 tokens, representing 10 successive requests or transactions. As seen in the illustration, the first request initiates three parallel processes, i.e. retrieval of data from the three aforementioned tables. If these three processes are completed before the next request (inquiry) is made, the process will end successfully and a result will be retuned to the user. However, if any of the three processes is still in progress while a new request is made then the current request may never get to completion. This is because every new incoming request is resetting the flag variable to zero, and no user request gets completed until the flag variable riches the value 3. Even if the three callbacks associated to a request get to completion, the caller process may still be on hold because the flag was reset by a subsequent request. According to this, if several subsequent requests overlap, some of them may wait forever to be allowed to complete. On the other hand, the reverse situation is also possible. When the flag gets to value 3 all requests that are on hold will be allowed to complete, even if some have not received all their data from the database. Either way the outcome of overlapping requests is mostly unpredictable and can result either in live-locks draining on the system’s resources or in the loss of the data that was meant to be sent to the client.
Although the experiment conducted with the Petri net model in this section is rather simplistic, nonetheless, it shows how the dynamic behavior of an application can be investigated through modeling and simulation. A few brief conclusions in this regard can be drawn here: first, the model animation shows visually what may happen with the envisioned application and where the application may enter into a live-lock situation or loss of data; second, the model can be run as long as it reveals that all the states of the model (application) are achievable and the consequences for each state are illustrated. Actually, the number of experiments depends on what the designer wants to study. In particular, we wanted to demonstrate to the students what will happen if a next request is made while the application is still processing the previous request. This is the most common situation with asynchronous applications.
Identifying the nature and the source of the problem with our application’s functionality was also the key for providing a working solution free of the above mentioned flaws. Namely the static flag variable behaves as an Application variable which means that if several clients make concurrent calls to the functions on this page some data may be lost due to concurrent increments of the flag variable by different users. What we need instead is a variable that not only has a different instance for each and every user, but for each and every request, even if coming from the same user.
It is now easy to implement a solution that is based on a separate class, called Flag, with a callBackCount object data member as synchronization variable. A new instance of this class and three instances of the MailMan class will be created for each user request. The three MailMan objects will reference the same Flag object and increment the same callBackCount variable. Since each request produces its own Flag object no interference between overlapping user requests is possible.
The elements of this alternative solution are shown bellow:
public class MailMan
{
public SqlCommand theCommand;
public DataTable theTable;
public Flag theFlag;
public MailMan(DataTable theTable, Flag theFlag)
{
this.theFlag = theFlag;
this.theTable = theTable;
}
}
public class Flag
{
public int callBackCount;
public Flag()
{
this.callBackCount = 0;
}
}
Increment the callBackCount variable in the Flag object through the object reference in the mailMan object associated to the callback:
//one done...
mailMan.theFlag.callBackCount++;
Hold the caller process until the callbacks complete:
//BEFORE closing the connection and
//BEFORE returning from here...
//wait for the callbacks to finish!!!
while (theFlagObject.callBackCount < 3) System.Threading.Thread.Sleep(10);
ajaxData.AcceptChanges();
sqlcn.Close();
9. Conclusions
In this paper we studied a simple example of the Supplier-Consumer Transactions (SCT) Application, where emerging software technologies such as AJAX and asynchronous database connections create a fundamentally different approach towards asynchronous application development. First, the underlying architectural model of the application becomes more complex and careful consideration of the available choices and their consequences is needed. We have also, through this simple example, established that the downside of these potential technologies is a much more complex pattern of interactions and that our current development tools are not well suited for dealing with it. We have the potential occurrence of hidden flaws that may not be easy to detect unless the application is tested over time in a real situation.
To test an application in a real situation is costly and unrealistic for many reasons, and often it may be just impossible. Imagine if an online shopping application fails for an hour or a day. This clearly makes a point for the need to have more powerful development, testing and debugging tools that will be able to address the challenges of asynchronous applications. The nature of these tools has yet to be investigated, but it is our belief that simulation tools, among other, will have an important role.
Modeling and simulation does the job of debugging and testing asynchronous applications at a far more affordable cost and in a controllable environment. For our sample application we implemented a custom mechanism to synchronize the callback functions with their caller. Although this approach worked well for single user requests, it simply ignored the possible interactions between successive user requests. A simple Petri net model simulation allowed us to detect a non-trivial flaw of our custom synchronization mechanism that was caused by overlapping user requests.
This paper is meant only as a preliminary inquiry into the issues of asynchronous applications, we do hope that it opens up an opportunity for more thorough study with complex applications. There is a clear need for a different perspective on the development process and tools for asynchronous applications where modeling and simulation would play a more important role.
References
Aalst, van der W.M.P. (2007). Trends in Business Process Analysis: From Validation to Process Mining. Proceedings of the International Conference on Enterprise Information Systems (ICEIS), Funchal, Madeira, Portugal, June 12-16.
Evjen Bill, Hanselman Scott, Muhammad Farhan, Sivakumar Srinivasa, Rader Devin (2006) Professional ASP.NET 2.0, Wrox Professional Guide, Wiley Publishing Inc.
Gibbs Matt, Wahlin Dan (2007) Professional ASP.NET 2.0 AJAX, Wiley Publishing, Inc.
McClure Wallace B., Cate Scott, Glavich Paul, Shoemaker Craig (2006) Beginning AJAX with ASP.NET, Wiley Publishing, Inc.
Gray Jim, Reuter Andreas (1993) Transaction Processing: Concepts and Techniques, Morgan Kaufmann Publishers, Inc.
Moore Dana, Budd Raymond, Benson Edward (2007) Professional Rich Internet Applications: AJAX and Beyond, Wiley Publishing, Inc.
Murata,T. (1989). Petri Nets: Properties, Analysis and Applications. Proceedings of the IEEE, v. 77, No 4
Pars Robin, Moroney Laurence, Grieb John (2007) Foundations of ASP.NET AJAX, Apress.
Peterson, J. L. (1981) Petri net theory and the modelling of systems. Prentice-Hall, Inc., Englewood Cliffs, NJ.
Reisig,W. (1985). Petri Nets, An Introduction. EATCS, Monographs on Theoretical Computer Science, W.Brauer, G. Rozenberg, A. Salomaa (Eds.), Springer Verlag, Berlin
Woolston Daniel (2006) Pro AJAX and the .NET 2.0 Platform, Apress.
Zakas Nicholas C., McPeak Jeremy, Fawcett Joe (2006) Professional AJAX, Wiley Publishing, Inc.
Share with your friends: |