在生產環境中測試,無水印。
在任何需要的地方都能運行。
獲得 30 天的全功能產品。
在幾分鐘內上手運行。
試用產品期間完全訪問我們的支援工程團隊
併發管理是 C# 高性能應用程式的一個關鍵方面。 它確保資源得到有效利用,同時避免潛在的衝突或性能瓶頸,因此使用輕量級的信號量來控制訪問可能非常有幫助。 這就是SemaphoreSlim發揮作用。 SemaphoreSlim 是一種輕量級同步基元,可控制資源訪問,最終防止競爭條件並確保執行緒安全。
那麼,如果您想與 PDF 庫一同實施這個以管理 PDF 生成過程,該怎麼辦? 您可能正在尋找一個強大的 PDF 庫,在這裡IronPDF進入。 IronPDF 是一個強大的 PDF 生成和操作庫,專為 .NET 開發人員設計,在多線程環境使用時可以大大受益於并發管理。
如果您想親眼見證 SemaphoreSlim 和 IronPDF 的運作,請務必繼續閱讀。我們將探討使用 SemaphoreSlim 的好處,以及如何將其與 IronPDF 集成,以安全處理並發操作、提高性能,並確保可靠的 PDF 處理。
SemaphoreSlim 是 .NET 中的一種同步原語,用於限制同時訪問特定資源或資源池的線程數量。 它是完整 Semaphore 類別的輕量化版本,專為在需要簡單、更快速信號量的情況下更加高效地運行而設計。
使用 SemaphoreSlim 的一些好處是,與 Semaphore 相比,系統開銷減少,這對於管理有限資源非常理想。(例如資料庫連接或檔案存取),並且支援非同步等待方法,非常適合現代 async/await 程式設計模式。
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
// Semaphore count
private static SemaphoreSlim semaphore = new SemaphoreSlim(3); // Limit to 3 concurrent threads.
static async Task Main(string[] args)
{
// Start tasks that will wait on the semaphore.
var tasks = new Task[5];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => AccessResource(i));
}
// Simulate some work in the main thread (e.g., initialization).
Console.WriteLine("Main thread is preparing resources...");
await Task.Delay(2000); // Simulate initialization delay.
// main thread calls release, releases semaphore permits to allow waiting tasks to proceed.
Console.WriteLine("Main thread releasing semaphore permits...");
semaphore.Release(2); // Releases 2 permits, allowing up to 2 tasks to proceed.
// Wait for all tasks to complete.
await Task.WhenAll(tasks);
Console.WriteLine("All tasks completed.");
}
static async Task AccessResource(int id)
{
Console.WriteLine($"Task {id} waiting to enter...");
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"current thread successfully entered by Task {id} .");
await Task.Delay(1000); // Simulate work.
}
finally
{
Console.WriteLine($"Task {id} releasing.");
_semaphore.Release();
}
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
// Semaphore count
private static SemaphoreSlim semaphore = new SemaphoreSlim(3); // Limit to 3 concurrent threads.
static async Task Main(string[] args)
{
// Start tasks that will wait on the semaphore.
var tasks = new Task[5];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => AccessResource(i));
}
// Simulate some work in the main thread (e.g., initialization).
Console.WriteLine("Main thread is preparing resources...");
await Task.Delay(2000); // Simulate initialization delay.
// main thread calls release, releases semaphore permits to allow waiting tasks to proceed.
Console.WriteLine("Main thread releasing semaphore permits...");
semaphore.Release(2); // Releases 2 permits, allowing up to 2 tasks to proceed.
// Wait for all tasks to complete.
await Task.WhenAll(tasks);
Console.WriteLine("All tasks completed.");
}
static async Task AccessResource(int id)
{
Console.WriteLine($"Task {id} waiting to enter...");
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"current thread successfully entered by Task {id} .");
await Task.Delay(1000); // Simulate work.
}
finally
{
Console.WriteLine($"Task {id} releasing.");
_semaphore.Release();
}
}
}
在程式執行過程中,當所有可用的許可證被執行緒獲得時,信號量的計數可以動態地達到零個執行緒。 此狀態表示已達到允許的最大併發訪問數量。
如果您願意,可以設置初始和最大執行緒數,將初始信號量計數設置為零,然後使用一個單獨的初始化任務在資源準備就緒時增加信號量計數,允許您選擇的執行緒數進行。 當信號量計數為零時,執行緒將會在嘗試進入信號量時等待,這被稱為「阻塞等待」。
您可以跟踪先前的信号量计数,以根据先前的计数调整信号量的行为,然後相应地操作信号量。(例如,通過釋放或等待). 隨著線程釋放,信號量計數會減少。
SemaphoreSlim 的一些常見用例包括:
要在多線程環境中開始使用IronPDF,首先安裝IronPDF NuGet 套件. 您可以藉由導航至工具 > NuGet 套件管理員 > 解決方案的 NuGet 套件管理員,並搜尋 IronPDF 來完成此操作:
或者,在套件管理控制台中執行以下命令:
Install-Package IronPdf
Install-Package IronPdf
要在程式碼中開始使用IronPDF,請確保您已將`using IronPdf`語句放在程式碼檔案的頂部。如需有關在您的環境中設置IronPDF的更深入指南,請查看其開始使用頁面。
當使用 SemaphoreSlim 時,您可以有效地控制對 PDF 生成任務的訪問。 這可確保您的應用程式不會嘗試同時生成過多的PDF,這可能會影響性能或導致故障。
以下範例代碼展示了 SemaphoreSlim 與 IronPDF 的基本用法。
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(2); // Limit to 2 concurrent threads.
static async Task Main(string[] args)
{
var tasks = new Task[5];
for (int i = 0; i < tasks.Length; i++)
{
string htmlContent = $"<h1>PDF Document {i}</h1><p>This is a sample PDF content for task {i}.</p>";
string outputPath = $"output_{i}.pdf";
// Start multiple tasks to demonstrate controlled concurrency.
tasks[i] = GeneratePdfAsync(htmlContent, outputPath, i);
}
await Task.WhenAll(tasks);
}
static async Task GeneratePdfAsync(string htmlContent, string outputPath, int taskId)
{
Console.WriteLine($"Task {taskId} is waiting for access...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"Task {taskId} has started PDF generation.");
ChromePdfRenderer renderer = new ChromePdfRenderer();
PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);
pdf.SaveAs(outputPath);
Console.WriteLine($"Task {taskId} has completed PDF generation.");
}
finally
{
// Ensure semaphore is released to allow other tasks to proceed.
_semaphore.Release();
Console.WriteLine($"Task {taskId} has released semaphore.");
}
}
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(2); // Limit to 2 concurrent threads.
static async Task Main(string[] args)
{
var tasks = new Task[5];
for (int i = 0; i < tasks.Length; i++)
{
string htmlContent = $"<h1>PDF Document {i}</h1><p>This is a sample PDF content for task {i}.</p>";
string outputPath = $"output_{i}.pdf";
// Start multiple tasks to demonstrate controlled concurrency.
tasks[i] = GeneratePdfAsync(htmlContent, outputPath, i);
}
await Task.WhenAll(tasks);
}
static async Task GeneratePdfAsync(string htmlContent, string outputPath, int taskId)
{
Console.WriteLine($"Task {taskId} is waiting for access...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"Task {taskId} has started PDF generation.");
ChromePdfRenderer renderer = new ChromePdfRenderer();
PdfDocument pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent);
pdf.SaveAs(outputPath);
Console.WriteLine($"Task {taskId} has completed PDF generation.");
}
finally
{
// Ensure semaphore is released to allow other tasks to proceed.
_semaphore.Release();
Console.WriteLine($"Task {taskId} has released semaphore.");
}
}
}
在此範例中,我們首先初始化了 SemaphoreSlim,並將 SemaphoreSlim 的初始和最大計數設置為「2」,限制其進行兩個同時的 PDF 生成。 接著我們創建了一個任務陣列,用來控制程式需要執行的任務數量,之後我們使用一個 for 迴圈根據任務陣列中的任務數量動態創建 PDF。
**WaitAsync()**\
方法然後用於進入信號量,然後使用 `Release()` 用在 finally 區塊中以確保即使發生異常時信號燈也能被釋放。 控制台輸出日誌顯示每個任務開始、結束及釋放信號量的時間,這允許您追蹤並發行為。
當多個執行緒與共享資源交互時,執行緒安全性至關重要。 在 PDF 操作中,SemaphoreSlim 確保只有定義數量的執行緒可以同時修改 PDF,以防止競爭條件並確保一致性。 在以下代碼中,我們正在模擬一個場景,在此場景中,我們向多個 PDF 添加浮水印,同時確保一次只進行一個操作。
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1);
static async Task Main(string[] args)
{
// Setting array of tasks
var tasks = new Task[3];
for (int i = 0; i < tasks.Length; i++)
{
string inputPath = $"input_{i}.pdf"; // Input PDF file path
string outputPath = $"output_{i}.pdf"; // Output PDF file path
string watermarkText = @"
<img src='https://ironsoftware.com/img/products/ironpdf-logo-text-dotnet.svg'>
<h1>Iron Software</h1>";
// Start multiple tasks to add watermarks concurrently.
tasks[i] = AddWatermarkAsync(inputPath, outputPath, watermarkText, i);
}
await Task.WhenAll(tasks); // Wait for all tasks to finish.
}
static async Task AddWatermarkAsync(string input, string outputPath, string watermark, int taskId)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is waiting to add a watermark...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is adding a watermark.");
var pdf = PdfDocument.FromFile(input);
pdf.ApplyWatermark(watermark); // Add watermark
pdf.SaveAs(outputPath); // Save the modified PDF
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has completed watermarking.");
}
finally
{
// Release the semaphore after the task is done.
_semaphore.Release();
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has released semaphore.");
}
}
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
class program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1);
static async Task Main(string[] args)
{
// Setting array of tasks
var tasks = new Task[3];
for (int i = 0; i < tasks.Length; i++)
{
string inputPath = $"input_{i}.pdf"; // Input PDF file path
string outputPath = $"output_{i}.pdf"; // Output PDF file path
string watermarkText = @"
<img src='https://ironsoftware.com/img/products/ironpdf-logo-text-dotnet.svg'>
<h1>Iron Software</h1>";
// Start multiple tasks to add watermarks concurrently.
tasks[i] = AddWatermarkAsync(inputPath, outputPath, watermarkText, i);
}
await Task.WhenAll(tasks); // Wait for all tasks to finish.
}
static async Task AddWatermarkAsync(string input, string outputPath, string watermark, int taskId)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is waiting to add a watermark...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} is adding a watermark.");
var pdf = PdfDocument.FromFile(input);
pdf.ApplyWatermark(watermark); // Add watermark
pdf.SaveAs(outputPath); // Save the modified PDF
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has completed watermarking.");
}
finally
{
// Release the semaphore after the task is done.
_semaphore.Release();
Console.WriteLine($"{DateTime.Now:HH:mm:ss} - Task {taskId} has released semaphore.");
}
}
}
通過將信號量計數設置為 1,使用 `private static SemaphoreSlim _semaphore = new SemaphoreSlim(1)`,我們確保一次只有一個任務可以操作 PDF。
IronPDF 擅長處理資源密集型任務,例如將大型 HTML 文件轉換為 PDF,並在異步環境中出色地完成這些任務。 使用 SemaphoreSlim 管理這些操作可確保您的應用程式即使在重負載下也能保持響應性而不會失去性能。
以下範例代碼展示了一種情境,我們需要限制 HTML 轉 PDF 的同時進行次數,以避免系統資源過載。
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
// Limit concurrent large PDF conversions to 2.
private static SemaphoreSlim _semaphore = new SemaphoreSlim(2);
static async Task Main(string[] args)
{
var tasks = new Task[4];
for (int i = 0; i < tasks.Length; i++)
{
string htmlContent = $"<h1>Large Document {i}</h1><p>Content for a large HTML file {i}.</p>";
string outputPath = $"large_output_{i}.pdf";
// Start multiple tasks to convert large HTML files to PDFs.
tasks[i] = ConvertLargeHtmlAsync(htmlContent, outputPath, i);
}
await Task.WhenAll(tasks); // Wait for all tasks to finish.
}
// Method to convert large HTML to PDF using SemaphoreSlim to control resource usage.
public static async Task ConvertLargeHtmlAsync(string htmlContent, string outputPath, int taskId)
{
Console.WriteLine($"Task {taskId} is waiting to start conversion...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"Task {taskId} is converting large HTML to PDF.");
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent); // Convert large HTML to PDF
pdf.SaveAs(outputPath); // Save the PDF file
Console.WriteLine($"Task {taskId} has completed conversion.");
}
finally
{
// Ensure the semaphore is released to allow other tasks to proceed.
_semaphore.Release();
Console.WriteLine($"Task {taskId} has released semaphore.");
}
}
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
// Limit concurrent large PDF conversions to 2.
private static SemaphoreSlim _semaphore = new SemaphoreSlim(2);
static async Task Main(string[] args)
{
var tasks = new Task[4];
for (int i = 0; i < tasks.Length; i++)
{
string htmlContent = $"<h1>Large Document {i}</h1><p>Content for a large HTML file {i}.</p>";
string outputPath = $"large_output_{i}.pdf";
// Start multiple tasks to convert large HTML files to PDFs.
tasks[i] = ConvertLargeHtmlAsync(htmlContent, outputPath, i);
}
await Task.WhenAll(tasks); // Wait for all tasks to finish.
}
// Method to convert large HTML to PDF using SemaphoreSlim to control resource usage.
public static async Task ConvertLargeHtmlAsync(string htmlContent, string outputPath, int taskId)
{
Console.WriteLine($"Task {taskId} is waiting to start conversion...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"Task {taskId} is converting large HTML to PDF.");
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(htmlContent); // Convert large HTML to PDF
pdf.SaveAs(outputPath); // Save the PDF file
Console.WriteLine($"Task {taskId} has completed conversion.");
}
finally
{
// Ensure the semaphore is released to allow other tasks to proceed.
_semaphore.Release();
Console.WriteLine($"Task {taskId} has released semaphore.");
}
}
}
在處理資源密集型任務(如將大型 HTML 文件轉換為 PDF)時,SemaphoreSlim 可以有助於平衡負載並優化資源使用。 通過設定 2 個同時操作的限制,我們防止系統因資源密集型的 PDF 生成任務而不堪重負。 這種方法有助於更均勻地分配工作負荷,從而提高整體應用程式的性能和穩定性。
如果信號量未正確釋放,可能會發生死鎖。 要注意的一個好做法是使用 try-finally 區塊,以確保在發生異常時仍能釋放信號量,防止死鎖並保持應用程式順暢運行。 避免死锁的一些最佳实践包括始终在 finally 块中释放信号量,以及避免使用像 `.wait` 这样的阻塞调用。()在您的異步代碼中使用 ` 和 `.Result`。
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(3);
static async Task Main(string[] args)
{
var tasks = new Task[3];
for (int i = 0; i < tasks.Length; i++)
{
string content = $"<h1>Document {i}</h1><p>Content for PDF {i}.</p>";
string path = $"safe_output_{i}.pdf";
// Start multiple tasks to demonstrate deadlock-free semaphore usage.
tasks[i] = SafePdfTaskAsync(content, path, i);
}
await Task.WhenAll(tasks); // Wait for all tasks to finish.
}
// Method demonstrating best practices for using SemaphoreSlim to avoid deadlocks.
public static async Task SafePdfTaskAsync(string content, string path, int taskId)
{
Console.WriteLine($"Task {taskId} is waiting to generate PDF...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"Task {taskId} is generating PDF.");
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(content); // Render HTML to PDF
pdf.SaveAs(path); // Save the PDF
Console.WriteLine($"Task {taskId} has completed PDF generation.");
}
catch (Exception ex)
{
Console.WriteLine($"Task {taskId} encountered an error: {ex.Message}");
}
finally
{
// Always release the semaphore, even if an error occurs.
_semaphore.Release();
Console.WriteLine($"Task {taskId} has released semaphore.");
}
}
}
using IronPdf;
using System;
using System.Threading;
using System.Threading.Tasks;
using IronPdf.Exceptions;
using System.Net.Http;
using System.Runtime.CompilerServices;
class program
{
private static SemaphoreSlim _semaphore = new SemaphoreSlim(3);
static async Task Main(string[] args)
{
var tasks = new Task[3];
for (int i = 0; i < tasks.Length; i++)
{
string content = $"<h1>Document {i}</h1><p>Content for PDF {i}.</p>";
string path = $"safe_output_{i}.pdf";
// Start multiple tasks to demonstrate deadlock-free semaphore usage.
tasks[i] = SafePdfTaskAsync(content, path, i);
}
await Task.WhenAll(tasks); // Wait for all tasks to finish.
}
// Method demonstrating best practices for using SemaphoreSlim to avoid deadlocks.
public static async Task SafePdfTaskAsync(string content, string path, int taskId)
{
Console.WriteLine($"Task {taskId} is waiting to generate PDF...");
// Wait to enter the semaphore.
await _semaphore.WaitAsync();
try
{
Console.WriteLine($"Task {taskId} is generating PDF.");
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(content); // Render HTML to PDF
pdf.SaveAs(path); // Save the PDF
Console.WriteLine($"Task {taskId} has completed PDF generation.");
}
catch (Exception ex)
{
Console.WriteLine($"Task {taskId} encountered an error: {ex.Message}");
}
finally
{
// Always release the semaphore, even if an error occurs.
_semaphore.Release();
Console.WriteLine($"Task {taskId} has released semaphore.");
}
}
}
通過使用`try-catch-finally`區塊,我們確保即使拋出異常,SemaphoreSlim對象也總是被釋放,從而防止死鎖。 透過記錄錯誤和適當管理信號量釋放,我們可以保持程式穩定並防止任何意外行為。
正如您在下面的輸出圖像中所見,我嘗試讓程序加載不存在的HTML文件以模擬錯誤,但即便出現此錯誤,程序仍然打印錯誤信息告訴我出了什麼問題,然後繼續使用finally區塊釋放信號量。
IronPDF旨在有效地處理並發PDF處理任務,提供優於許多其他PDF庫的性能和可靠性。 其穩健的架構使其能夠隨著應用程式的需求進行擴展,非常適合高需求的環境。 與其他基於性能、易用性和穩健性標準的PDF庫相比,IronPDF證明是一個強大的競爭者。 為了展示這一點,我將IronPDF與其他幾個流行的PDF庫進行了比較,如iTextSharp、PDFsharp、DinkToPdf和EvoPDF:
IronPDF:
非同步作業: 支援非同步 PDF 生成,在響應性至關重要的網路應用程式中實現更好的效能。
iTextSharp:
資源管理: 使用 iTextSharp 的記憶體使用量可能較高,特別是在處理大型文件或進行複雜操作時,這可能在某些情況下導致性能瓶頸。
PDFsharp:
資源管理: 它的記憶體使用未經過充分優化,可能在處理大型檔案或包含大量圖像的文件時遇到困難。
DinkToPdf:
資源管理: 它通常需要大量記憶體和處理能力,並且缺乏對非同步操作的原生支持,從而限制了在高負載情況下的性能。
EvoPDF:
IronPDF:
安裝和整合: 可通過 NuGet 輕鬆安裝,並順利整合到現有的 .NET 專案中,所需配置最少。
iTextSharp:
安裝和整合: 可通過NuGet獲得,但需要更深入了解API以便有效整合。
PDFsharp:
安裝和整合: 通過 NuGet 安裝簡單,但提供有限的 HTML 到 PDF 功能。
DinkToPdf:
安裝和整合: 可能安裝起來較為複雜,需要額外的依賴項,例如 wkhtmltopdf,這可能會使設置變得更複雜。
EvoPDF:
IronPDF:
兼容性: 完全兼容 .NET Core、.NET 5+ 及傳統 .NET Framework 版本,使其在不同項目類型中具有多功能性。
iTextSharp:
相容性: 非常適合多種環境,包括 .NET Framework 和 .NET Core。
PDFsharp:
相容性: 與 .NET Framework 及 .NET Core 相容,但進階功能有限。
DinkToPdf:
相容性: 與 .NET Core 和 .NET Framework 兼容,但需要外部依賴性,這可能會引發相容性問題。
EvoPDF:
IronPDF 無縫整合至非同步程式設計模型,補充如 SemaphoreSlim 之類的併發控制機制。 這使開發者能夠以最少的努力構建響應迅速且性能友好的應用程式。
IronPDF 也提供廣泛的文件和支援資源,以幫助開發人員理解和實施有效的錯誤處理慣例。 這種全面的支持對於在 .NET 項目中進行故障排除和優化 PDF 操作具有很大價值。
IronPDF 提供:
PDF API 參考:提供 API 參考,讓您充分發揮我們工具所提供的功能。
如需更多資訊,請查看 IronPDF 的廣泛資料文檔.
在 .NET 應用程式中使用 SemaphoreSlim 進行並發管理是至關重要的,特別是在處理像 PDF 處理這樣的資源密集型任務時。 通過將 SemaphoreSlim 與 IronPDF 集成,開發人員可以實現安全、高效且可靠的併發控制,確保其應用程式保持響應性並具有良好的性能。
探索 IronPDF 如何簡化您的 PDF 處理工作流程。 親自嘗試一下它的免費試用如果您希望在項目中繼續使用這個強大的工具,起價僅為 $749。