Large Microsoft Project MPP and Primavera XER files can take anywhere from a few seconds to several minutes to parse. While that runs, a naive application looks frozen: no progress bar, no status text, nothing the user can act on. Anyone loading big schedules needs a way to show how far along a load is, and a way to stop one that runs too long.
This guide covers both with Aspose.Tasks for .NET. You will build a progress callback that reports the current phase and a percentage, wire it into the load, keep a desktop UI responsive while parsing runs on a background thread, and cancel a load that exceeds a time budget.
Key Takeaways
IProgressNotificationCallback.Notify(ProgressNotificationArgs)is the single method you implement to receive load progress from Aspose.Tasks for .NET.ProgressNotificationArgsexposesCurrentStepName,CurrentStepProgress(0–100), andEstimatedTotalProgress(0–100).- Attach the callback with
LoadOptions.ProjectLoadingCallback; it is supported for the MPP and XER formats. Notifyruns on the loading thread, so marshal values to the UI thread before updating a control.- Cancel a slow load by setting
LoadOptions.CancellationTokenand callingCancel()on itsCancellationTokenSourcefrom another thread. - The progress API works in evaluation mode; a license only removes file-size and output limits.
New to the library? The Aspose.Tasks for .NET product page and the documentation cover setup and licensing in full.
How to Monitor Project Load Progress in C#?
Implement IProgressNotificationCallback, set it on LoadOptions.ProjectLoadingCallback, and load the file through the Project constructor that accepts LoadOptions. The minimum working code is three parts: a callback class, a LoadOptions instance, and the load call.
- Input: an MPP or XER file, for example
BigProject.mpp - Output: progress updates during parsing —
CurrentStepName,CurrentStepProgress,EstimatedTotalProgress - Library: Aspose.Tasks for .NET
- Language: C#
using System;
using Aspose.Tasks;
internal sealed class ConsoleProgressCallback : IProgressNotificationCallback
{
public void Notify(ProgressNotificationArgs args)
{
Console.WriteLine(
"{0}: step {1}% / total {2}%",
args.CurrentStepName,
args.CurrentStepProgress,
args.EstimatedTotalProgress);
}
}
internal static class Program
{
private static void Main()
{
// A license is optional here. Uncomment to remove evaluation limits.
// new License().SetLicense("Aspose.Tasks.lic");
var loadOptions = new LoadOptions
{
ProjectLoadingCallback = new ConsoleProgressCallback()
};
var project = new Project("BigProject.mpp", loadOptions);
Console.WriteLine("Loaded {0} top-level tasks.", project.RootTask.Children.Count);
}
}
As the file is parsed, Notify runs several times and the console shows the phase and percentages. After the constructor returns, the Project object is fully loaded and usable.
Why Does Load Progress Monitoring Matter for Large Files?
It replaces an unresponsive-looking pause with visible feedback, which is the difference between an application that looks broken and one that looks busy. The larger the file, the longer the parse, and the more a blind load costs you in perceived quality.
Concrete uses for the progress values:
- Drive a determinate progress bar from
EstimatedTotalProgressinstead of showing an indefinite spinner. - Log
CurrentStepNametransitions to find which phase dominates load time when you profile a slow file. - Keep the UI responsive by loading on a background thread and posting progress to the UI thread.
- Show per-file progress in a batch tool that converts or migrates many projects in sequence.
How Do You Install Aspose.Tasks and Set Up the Project?
Add the Aspose.Tasks NuGet package to a .NET 6 or later project.
dotnet add package Aspose.Tasks
Or, from the Visual Studio Package Manager Console:
Install-Package Aspose.Tasks
The progress callback works without a license. If you need to load files above the evaluation size limit or process without evaluation restrictions, set a license once at startup:
using Aspose.Tasks;
var license = new License();
license.SetLicense("Aspose.Tasks.lic");
You can request a free temporary license from the Aspose temporary license page, and read more about the library on the Aspose.Tasks for .NET product page.
What Does the IProgressNotificationCallback API provide?
The API is one interface, one method, and one argument type, all in the Aspose.Tasks namespace.
| Member | Type | Description |
|---|---|---|
IProgressNotificationCallback.Notify(ProgressNotificationArgs) | void | Called during long-running project operations to report progress. |
ProgressNotificationArgs.CurrentStepName | string (get) | Name of the current phase of the operation. |
ProgressNotificationArgs.CurrentStepProgress | int (get) | Estimated percentage complete for the current phase, 0–100. |
ProgressNotificationArgs.EstimatedTotalProgress | int (get) | Estimated percentage complete for the whole operation, 0–100. |
LoadOptions.ProjectLoadingCallback | IProgressNotificationCallback (get/set) | The callback invoked during loading. Supported for MPP and XER. |
LoadOptions.CancellationToken | System.Threading.CancellationToken (get/set) | Token used to cancel a load in progress. |
ProgressNotificationArgs is sealed and derives from EventArgs. All three of its properties are read-only.
How Do You Implement a Progress Notification Callback?
Create a class that implements IProgressNotificationCallback and do something useful with the ProgressNotificationArgs inside Notify. Keep the method fast, because it runs on the loading thread.
using System;
using Aspose.Tasks;
internal sealed class ConsoleProgressCallback : IProgressNotificationCallback
{
private int lastTotal = -1;
public void Notify(ProgressNotificationArgs args)
{
// Only redraw when the total percentage actually changes.
if (args.EstimatedTotalProgress == lastTotal)
{
return;
}
lastTotal = args.EstimatedTotalProgress;
Console.WriteLine("[{0,3}%] {1}", args.EstimatedTotalProgress, args.CurrentStepName);
}
}
The guard on lastTotal avoids flooding the console with duplicate lines when several Notify calls report the same total.
How Do You Load a Project with Progress Tracking?
Set ProjectLoadingCallback on a LoadOptions object, then pass it to the Project constructor. The load runs synchronously; when the constructor returns, parsing is complete.
using System;
using Aspose.Tasks;
internal static class Loader
{
public static Project Load(string path)
{
var loadOptions = new LoadOptions
{
ProjectLoadingCallback = new ConsoleProgressCallback()
};
// Same call for MPP and XER; only the file path changes.
var project = new Project(path, loadOptions);
Console.WriteLine("Done. Root task children: {0}", project.RootTask.Children.Count);
return project;
}
}
Trying this on your own schedules? Download a free temporary license to run loads without the evaluation limit on file size.
How Do You Keep a UI Responsive While Loading?
Load on a background thread and marshal the progress values to the UI thread inside the callback, because Notify runs on whichever thread called the Project constructor.
using System.Windows.Forms;
using Aspose.Tasks;
internal sealed class ProgressBarCallback : IProgressNotificationCallback
{
private readonly ProgressBar bar;
public ProgressBarCallback(ProgressBar bar) => this.bar = bar;
public void Notify(ProgressNotificationArgs args)
{
if (bar.InvokeRequired)
{
bar.BeginInvoke(() => bar.Value = args.EstimatedTotalProgress);
}
else
{
bar.Value = args.EstimatedTotalProgress;
}
}
}
In WPF, use Dispatcher.Invoke or Dispatcher.BeginInvoke in place of Control.BeginInvoke.
How do You Cancel a Long-Running Load?
Set LoadOptions.CancellationToken to a token from a CancellationTokenSource, then call Cancel() on that source from another thread. Aspose.Tasks stops parsing and the Project constructor throws, so wrap it in try/catch.
using System;
using System.Threading;
using Aspose.Tasks;
var cts = new CancellationTokenSource();
var loadOptions = new LoadOptions
{
ProjectLoadingCallback = new ConsoleProgressCallback(),
CancellationToken = cts.Token
};
// Wire a Cancel button or a timeout to this:
// cts.CancelAfter(TimeSpan.FromSeconds(30));
try
{
var project = new Project("BigProject.xer", loadOptions);
Console.WriteLine("Loaded {0} tasks.", project.RootTask.Children.Count);
}
catch (Exception ex)
{
Console.WriteLine("Load stopped: {0}", ex.Message);
}
Conclusion
Adding load progress to an Aspose.Tasks for .NET application is a small change with a large effect on perceived responsiveness. Implement IProgressNotificationCallback, assign it to LoadOptions.ProjectLoadingCallback, and read CurrentStepName, CurrentStepProgress, and EstimatedTotalProgress inside Notify. Add a CancellationToken when loads can run long enough that a user might want to stop them.
Explore the Aspose.Tasks for .NET documentation and the API reference to go further, and download a free temporary license to evaluate without limits.
FAQs
Do I need a paid license to use the progress notification API?
No. IProgressNotificationCallback works in evaluation mode. A temporary or paid license only removes the evaluation limits on file size and output; it does not unlock the progress API.
Does the same callback work for XER files as well as MPP?
Yes. LoadOptions.ProjectLoadingCallback is currently supported for the MPP and XER formats. The same IProgressNotificationCallback implementation handles both.
Is the progress callback thread-safe?
Notify is invoked on the thread that runs the load, not a background thread. In a desktop app, marshal the values to the UI thread with Control.Invoke or Dispatcher.Invoke before touching a progress bar.
What is the difference between CurrentStepProgress and EstimatedTotalProgress?
CurrentStepProgress is the percentage complete for the current phase of the load, such as reading tasks or reading resources. EstimatedTotalProgress is the estimated percentage complete for the whole operation. Both are integers from 0 to 100.
Can I cancel a load from inside the callback?
Not from inside Notify directly. Instead, set LoadOptions.CancellationToken to a token from a CancellationTokenSource and call Cancel() on that source from another thread. The Project constructor then stops and throws, so wrap it in try/catch.
Does adding a callback slow down loading?
The callback overhead is negligible because Notify is called at a small number of checkpoints, not per record. Keep the method lightweight, though, since any work you do inside it runs on the loading thread.
