How to: Use the QueueSystem Web Service

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Microsoft Office Project Server 2007 includes the Project Server Queuing service to handle jobs that can be long-running and to manage many simultaneous jobs. Project applications often need to determine when a job sent to the Project Server queue is complete, to continue with a process. This article show how to develop a method that uses the QueueSystem Web service to wait until a job is completed.

The Project Server Interface (PSI) includes many methods that begin with the name Queue, such as QueueSubmitTimesheet, QueueArchiveCustomFields, and QueueCreateProject. All asynchronous PSI methods use the Project Server Queuing Service and have names that begin with Queue. If the PSI method name does not begin with Queue, then the method is synchronous. The .NET Framework automatically adds asynchronous methods when wsdl.exe generates the Web service proxies; those auto-generated proxy methods do not use the Project Server Queuing service. For more information about the Asynch methods, see Using the PSI in Visual Studio.

Project Server has two queues:

  • The Timesheet queue manages jobs from queue-based methods in the Timesheet Web service.

  • The Project queue manages jobs such as save and publish from the other queue-based Web methods.

The QueueSystem class in the PSI includes methods that get an estimate of how long a job is going to take, find the job status, and cancel a job. The queue-based methods send a message with a job ID to the Project Server Queuing service to process a job. If the method spawns only one queue job, the method parameter includes the JobUid parameter. JobUid can be used for finding the job status in the queue. Some methods, such as QueueArchiveProjects and QueueUpgradeProject, spawn more than one queue job; they do not expose the JobUid parameter. For correlated jobs, the queue creates a job correlation GUID which you can use with methods such as GetJobCount, UnblockCorrelation, RetryCorrelation, GetProposedJobWaitTime, QueuePurgeArchivedJobs, and CancelCorrelation.

To track methods that spawn more than one job, you can use any of the methods that return a QueueStatusDataSet, such as ReadMyJobStatus.

To track multiple jobs that aren't necessarily correlated, set a tracking GUID on the SOAP header and use it to track the job group in the queue. A tracking GUID is the JobGroupGUID property in the QueueStatusDataSet and the QueueStatusRequestDataSet. For an example of how to set a tracking GUID on multiple jobs and how to use the QueueStatusRequestDataSet, see ReadJobStatus. For other examples that use a tracking GUID, see GetJobGroupWaitTime and GetJobGroupWaitTimeSimple.

This article shows how to create a queue utilities class and method that determines when a specified queue job is either complete or has exceeded a specified time, or when the job has an error condition in the Project Server Queuing service.

Important noteImportant

WaitForQueue uses the Thread.Sleep method between checks for the queue job status. Blocking execution of a process in the server thread pool for a long duration or for many simultaneous processes, such as submitting timesheets, can severely impact the performance of Project Server. We recommend the WaitForQueue method for limited use in client applications only, not for server components.

Server components should not use Thread.Sleep in a tight loop such as in the WaitForQueue sample. Instead, an application that uses a server component for calls to PSI queue-based methods should display the job status and let the user refresh the application to update the job status field.

The sample WaitForQueue method includes a timeOut parameter so you can specify the maximum number of seconds to wait and cancel the job if it exceeds the time. The sample code returns the job status, including any error condition. To help show how the Project Server queue works, the WaitForQueue method also returns the initial Project Server Queuing service estimate of the number of seconds to complete the job and the actual number of seconds.

For the complete sample code of the WaitForQueue method, see Example.

Waiting for a Queue Job

Procedure 1 shows how to create a queue utilities class with a WaitForQueue method that returns status and error information. Procedure 2 shows how to call the method from an application. For examples of status output, see Comments.

Procedure 1. To create a utility method that waits for a queue job:

  1. Add a class to your application, for example QueueSystemUtils. Add the class constructor and necessary references, as in the following example. Your namespace will be different.

    The INCREMENTALSLEEPTIME constant should be as many seconds as it takes a typical short queue job to complete.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    using System.Xml; 
    using System.Threading;
    
    namespace SomeNamespace
    {
        class QueueSystemUtils
        {
            // Wait 2 seconds between each check for job completion
            private const int INCREMENTALSLEEPTIME = 2; 
    
            public QueueSystemUtils()
            {
            }
    
            /* Add WaitForQueue method here */
        }
    }
    
  2. Set a Web reference in the application to the QueueSystem Web service; for example, name the reference WebSvcQueueSystem.

  3. Add a WaitForQueue method to the QueueSystemUtils class, which returns true for success and false for an error. WaitForQueue has the following parameters:

    • q is an instance of the QueueSystem class in the WebSvcQueueSystem namespace.

    • timeOut is the maximum number of seconds to wait for the queue job to complete.

    • jobId is the GUID of the queue job.

    • statusOut is an out parameter for the queue status. The sample method assigns the estimate of time to wait and the total actual time waited to statusOut, as well as the XML string of errors from the queue system.

    public bool WaitForQueue(WebSvcQueueSystem.QueueSystem q, 
        int timeOut, 
        Guid jobId, 
        out String statusOut)
    {
    
        int wait;                 // Number of seconds to wait
        decimal seconds;          // For reporting wait time in decimal
                                  // format
        string xmlError;          // XML error output from the queue
        string queueStatus;       // Outer XML of xmlError string
        string status = "";       // Summary status report for output
        bool firstPass = true;    // First iteration through the while 
                                  // statement
        int timeSlept = 0;        // Total time slept (seconds)
        bool jobIsDone = false;   // The queue job completed 
                                  // successfully, if true
        bool stopWait = false;    // Abort the wait, if true
        WebSvcQueueSystem.JobState jobState; // Status of the queue job
    
        while (true)
        {
            // On the first iteration, wait the incremental sleep time 
            // or the maximum requested timeout. 
            if (firstPass)
            {
                // Get the estimated time to wait for the queue to 
                // process the job.
                // The output from GetJobWaitTime is in seconds.
                wait = q.GetJobWaitTime(jobId);
    
                status = string.Format("Estimated job wait time: {0} seconds", wait);
    
                if (timeOut < INCREMENTALSLEEPTIME) wait = timeOut;
                else wait = INCREMENTALSLEEPTIME;
    
                firstPass = false;
            }
            else
            {
                // If job is not done, wait the incremental sleep time
                wait = INCREMENTALSLEEPTIME;
            }
    
            Thread.Sleep(wait * 1000); // Milliseconds
    
            timeSlept += wait;
    
            // Check job state
            jobState = q.GetJobCompletionState(jobId, out xmlError);
    
            // Add the XML error output to the status
            StringReader sr = new StringReader(xmlError);
            using (XmlReader reader = XmlReader.Create(sr))
            {
                reader.MoveToContent();
                queueStatus = reader.ReadOuterXml();
            }
            // Don't add an empty <errinfo> tag
            if (queueStatus != "<errinfo />") status += "\n\n" + queueStatus; 
            if (jobState == WebSvcQueueSystem.JobState.Success)
            {
                jobIsDone = true;
            }
            else if (jobState == WebSvcQueueSystem.JobState.Unknown
                || jobState == WebSvcQueueSystem.JobState.Failed
                || jobState == WebSvcQueueSystem.JobState.FailedNotBlocking
                || jobState == WebSvcQueueSystem.JobState.CorrelationBlocked
                || jobState == WebSvcQueueSystem.JobState.Canceled)
            {
                stopWait = true;
            }
    
            if (!jobIsDone && timeSlept >= timeOut)
            {
                // Cancel the job, otherwise the queue keeps processing 
                // until the job is complete.
                q.CancelJobSimple(jobId);
                stopWait = true;
                status += string.Format("\n\nExceeded timeout of {0} seconds", timeOut);
            }
    
            if (jobIsDone || stopWait)
            {
                seconds = Convert.ToDecimal(timeSlept);
                status += string.Format(
                    "\n\nJobState: {0:G}\n\nTotal time slept: {1:N} seconds", 
                    jobState, seconds);
                break;
            }
        }
        statusOut = status;
        return jobIsDone;
    }
    

When you add the QueueSystemUtils class to an application, modify the class namespace, add code to instantiate the class and call the WaitForQueue method, and then test the application. For example, Procedure 2 shows how you can use the QueueSystemUtils class in the application described in How to: Log on to Project Server Programmatically.

Procedure 2. To call the WaitForQueue method:

  1. Change the namespace of the QueueSystemUtils class to the application namespace. For example, change the class namespace to LoginDemo.

  2. Create a Web reference to the QueueSystem Web service. For example, name the Web service namespace WebSvcQueueSystem.

  3. Set class constants and variables for the Web service and the results of the WaitForQueue method, and then instantiate the QueueSystem class, as follows.

    private const string QUEUESYSTEMWEBSERVICE = "_vti_bin/PSI/QueueSystem.asmx";
    private int timeout;
    public static WebSvcQueueSystem.QueueSystem queueSystem =
        new WebSvcQueueSystem.QueueSystem();
    private static QueueSystemUtils queueSystemUtils = 
        new QueueSystemUtils();
    
  4. Add the Url property and the Windows credentials for the Project Server forms logon cookie to the queueSystem object. The following code shows lined added to the AddContextInfo method in the LogonProjectServer.cs sample, in How to: Log on to Project Server Programmatically.

    // The BaseUrl property is, for example: http://ServerName/ProjectServerName/
    queueSystem.Url = loginUtils.BaseUrl + QUEUESYSTEMWEBSERVICE;
    queueSystem.Credentials = CredentialCache.DefaultCredentials;
    queueSystem.CookieContainer = loginUtils.Cookies;
    
  5. After you create a job GUID and call a queue-based method in your application, add the code to call WaitForQueue and handle the results for both success and failure. For example, the btnCreateProject_Click event handler sample code in LogonProjectServer.cs calls QueueCreateProject. The application must wait for the queue to add the project to the Draft database before calling QueuePublish. Comment out the System.Threading.Thread.Sleep(3000) line in the sample and add code after calling QueueCreateProject to test the WaitForQueue method, as follows:

    project.QueueCreateProject(jobGuid, dsProject, validateOnly);
    //System.Threading.Thread.Sleep(3000);
    timeout = 5;  // Wait a maximum of 5 seconds for the queue job.
                  // Or, add a text box to the application so you can 
                  // interactively set the timeout value for testing.
    if (queueSystemUtils.WaitForQueue(queueSystem, timeout, jobGuid, 
        out queueStatus))
    {
        MessageBox.Show(queueStatus, "Queue Status",
            MessageBoxButtons.OK, MessageBoxIcon.Information);
    
        // Initialize the ProjectRelationsDataSet and the parameters for QueuePublish
        dsProjectRelations = project.QueuePublish(jobGuid, projectGuid, 
            fullPublish, wssUrl);
        created = true;
    }
    else
    {
        created = false;
        MessageBox.Show(queueStatus, "Queue Error", 
            MessageBoxButtons.OK, MessageBoxIcon.Warning);
    }
    

    The preceding code is an example for testing. In a production application you would likely not use a message box to show the queue status for a successful job. You could handle a failed job in other ways: for example, add information to a log file and create an application event for the Windows Event Viewer.

Example

Following is the complete code for the WaitForQueue method in a class named QueueSystemUtils.

NoteNote

WaitForQueue is designed for limited use in client applications. You should not use tight loops with calls to Thread.Sleep in server-side components, to avoid adversely affecting the server thread pool and degrading server performance.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Threading;

namespace SomeNamespace
{
    class QueueSystemUtils
    {
        // Wait 2 seconds between each check for job completion
        private const int INCREMENTALSLEEPTIME = 2; 

        public QueueSystemUtils()
        {
        }

        /// <summary>
        /// Wait a specified time for the Project Server Queuing service to process a job.
        /// </summary>
        /// <param name="q">QueueSystem object</param>
        /// <param name="timeOut">Maximum time to wait (seconds)</param>
        /// <param name="jobId">GUID of queue job</param>
        /// <param name="errorString">out: error from GetJobCompletionState plus status</param>
        /// <returns>true for success; false for any queue failure</returns>
        public bool WaitForQueue(WebSvcQueueSystem.QueueSystem q, 
            int timeOut, 
            Guid jobId, 
            out String statusOut)
        {
            
            int wait;                 // Number of seconds to wait
            decimal seconds;          // For reporting wait time in decimal format
            string xmlError;          // XML error output from the queue
            string queueStatus;       // Outer XML of xmlError string
            string status = "";       // Summary status report for output
            bool firstPass = true;    // First iteration through the while statement
            int timeSlept = 0;        // Total time slept (seconds)
            bool jobIsDone = false;   // The queue job completed successfully, if true
            bool stopWait = false;    // Abort the wait, if true
            WebSvcQueueSystem.JobState jobState; // Status of the queue job 

            while (true)
            {
                // On the first iteration, wait the incremental sleep time  
                // or the maximum requested timeout. 
                if (firstPass)
                {
                    // Get the estimated time to wait for the queue to process the job.
                    // The output from GetJobWaitTime is in seconds.
                    wait = q.GetJobWaitTime(jobId);

                    status = string.Format("Estimated job wait time: {0} seconds", wait);

                    if (timeOut < INCREMENTALSLEEPTIME) wait = timeOut;
                    else wait = INCREMENTALSLEEPTIME;

                    firstPass = false;
                }
                else
                {
                    // If job is not done, wait the incremental sleep time  
                    wait = INCREMENTALSLEEPTIME;
                }

                Thread.Sleep(wait * 1000); // Milliseconds

                timeSlept += wait;

                // Check job state
                jobState = q.GetJobCompletionState(jobId, out xmlError);

                // Add the XML error output to the status 
             StringReader sr = new StringReader(xmlError); 
             using (XmlReader reader = XmlReader.Create(sr))
             {
                 reader.MoveToContent();
                 queueStatus = reader.ReadOuterXml();
             }
             // Don't add an empty <errinfo> tag
             if (queueStatus != "<errinfo />") status += "\n\n" + queueStatus; 

                if (jobState == WebSvcQueueSystem.JobState.Success)
                {
                    jobIsDone = true;
                }
                else if (jobState == WebSvcQueueSystem.JobState.Unknown
                    || jobState == WebSvcQueueSystem.JobState.Failed
                    || jobState == WebSvcQueueSystem.JobState.FailedNotBlocking
                    || jobState == WebSvcQueueSystem.JobState.CorrelationBlocked
                    || jobState == WebSvcQueueSystem.JobState.Canceled)
                {
                    stopWait = true;
                }

                if (!jobIsDone && timeSlept >= timeOut)
                {
                    // Cancel the job, otherwise the queue keeps 
                    // processing until the job is complete.
                    q.CancelJobSimple(jobId);
                    stopWait = true;
                    status += string.Format("\n\nExceeded timeout of {0} seconds", 
                        timeOut);
                }

                if (jobIsDone || stopWait)
                {
                    seconds = Convert.ToDecimal(timeSlept);
                    status += string.Format(
                        "\n\nJobState: {0:G}\n\nTotal time slept: {1:N} seconds", 
                        jobState, seconds);
                    break;
                }
            }
            statusOut = status;
            return jobIsDone;
        }
    }
}

The WaitForQueue method is a sample for test purposes. The GetJobCompletionState method returns error information in XML format in the xmlError parameter. If there are no errors, xmlError parameter contains the following:

<?xml version="1.0" encoding="utf-8"?>
<errinfo />

The sample code adds the errinfo element only if it contains error data. If you want the statusOut parameter to return the estimated wait time and actual wait time as well-formed XML, you could add elements to the xmlError output from GetJobCompletionState so that the application could parse the entire statusOut results using XmlReader.

Following are examples of statusOut results for the sample WaitForQueue method in the LoginDemo test application. The initial estimates of time for the queue to process a QueueCreateProject job are often high when Project Server is newly installed. As Project Server continues to process jobs, the accuracy of the estimates from GetJobWaitTime improves.

  • Normal completion:

    Estimated job wait time: 12 seconds
    JobState: Success
    Total time slept: 4.00 seconds
    
  • Set timeout to one second:

    Estimated job wait time: 12 seconds
    Exceeded timeout of 1 seconds
    JobState: Processing
    Total time slept: 1.00 seconds
    
  • Try to create a project that already exists. The error ID 1034 means the project name already exists; error 26000 means the queue job failed. See Project Server Error Codes.

    Estimated job wait time: 16 seconds
    
    <errinfo>
      <general>
        <class name="Project">
          <error id="1034" uid="17f19b07-1bac-452d-8531-9fe038a9e8ef" 
                 projName="Test Q 5" />
        </class>
        <class name="Queue">
          <error id="26000" uid="fdfc50dc-0931-4496-a559-7ff87f02cb51" 
                 JobUID="43f96ee4-e771-4fa7-a808-db7ba2a69705" 
                 ComputerName="ComputerName" />
        </class>
      </general>
    </errinfo>
    
    JobState: Failed
    Total time slept: 2.00 seconds
    
  • Open the Services window and double-click the Microsoft Project Server Queuing (<SharedServicesName>) service. Set the Startup type to Disabled, click Apply, and then click Stop. Set the timeout to 20 seconds, and then try to create a project using the test application.

    Estimated job wait time: 11 seconds
    Exceeded timeout of 20 seconds
    JobState: ReadyForProcessing
    Total time slept: 20.00 seconds
    

Security

To use the QueueSystem methods that check a job status or cancel a job, the user must have the ManageQueue global permission or own the job.

See Also

Tasks

How to: Log on to Project Server Programmatically

Reference

ReadMyJobStatus

ReadJobStatus

GetJobGroupWaitTimeSimple

Project Server Error Codes

Concepts

Office Project 2007 Platform

Using the PSI in Visual Studio