Chilkat Async in C#

Chilkat’s Async model is a custom, cross-platform architecture designed to provide consistent asynchronous (background thread) execution across dozens of programming languages (C#, Java, Python, C++, Delphi, etc.).

Because Chilkat is fundamentally a C++ core library wrapped for various languages, it implements its own internal thread management. This is why its async model does not use C#'s native System.Threading.Tasks.Task or the standard async/await state machine. Instead, it uses its own Chilkat.Task object.

Here is a complete overview of how the Chilkat Async model works, its lifecycle, and how you use it in both .NET Framework and .NET Core.


The 4-Step Lifecycle of a Chilkat Task

Every asynchronous operation in Chilkat follows the exact same 4-step pattern:

  1. Creation: You call a method ending in Async (e.g., socket.ConnectAsync()). This method does not start the operation; it simply creates and returns a dormant Chilkat.Task object containing the context of what needs to be done.
  2. Execution: You must explicitly call task.Run() to start the task. Once called, Chilkat spins up an internal background thread and begins processing.
  3. Monitoring: The application must wait for the task to finish. You can do this by polling (task.Finished), waiting (task.Wait()), or listening for an event/callback.
  4. Harvesting: Because a Chilkat.Task is a generic object, it doesn't have a generic type parameter like C#'s Task<T>. Instead, once the task finishes, you retrieve the final result by calling a type-specific method like task.GetResultBool(), task.GetResultString(), or task.GetResultBytes().

Method 1: Polling / Manual Wait

In this approach, you start the task and periodically check if it is done. This is useful in background services or worker threads where you can afford to safely block or loop.

Chilkat.Socket socket = new Chilkat.Socket();

// 1. Create the task (Does NOT start it yet)
// Let's assume we are trying to connect to a server with a 5000ms timeout
Chilkat.Task task = socket.ConnectAsync("example.com", 443, true, 5000);

// 2. Start the background thread
task.Run();

// 3. Monitor for completion
// Loop until Finished is true. You can do other work here.
while (task.Finished != true) 
{
    // Sleep briefly to prevent high CPU usage
    System.Threading.Thread.Sleep(10); 
}

// 4. Harvest the result
if (task.TaskSuccess == true) 
{
    // Connect returns a boolean, so we use GetResultBool()
    bool success = task.GetResultBool(); 
    Console.WriteLine($"Connection successful: {success}");
}
else 
{
    Console.WriteLine($"Task failed. Error: {task.ResultErrorText}");
}

(Note: You can also just call task.Wait(timeoutMs) if you want to block the current thread until the task finishes.)


Method 2: The Event-Driven / Callback Approach

If you don't want to poll, you can have Chilkat notify your application when the task completes. However, how you implement this depends on whether you are using the older .NET Framework or the newer .NET Core / .NET 5+ environments.

Because Chilkat had to adapt to support modern cross-platform execution (Linux, macOS) without relying on traditional Windows COM-style event marshaling, the .NET Core implementation requires an explicit callback and task-tracking model.

Option 2A: The .NET Framework Approach (Traditional Events)

In .NET Framework, Chilkat classes expose traditional C# events. You subscribe to the event using +=, and when the event fires, the TaskCompletedEventArgs provides direct access to the finished Chilkat.Task.

using System;

public class ChilkatFrameworkExample
{
    public void ConnectAsync()
    {
        Chilkat.Socket socket = new Chilkat.Socket();
        
        // 1. Subscribe to the standard C# event
        socket.OnTaskCompleted += Socket_OnTaskCompleted;

        // 2. Create the task
        Chilkat.Task task = socket.ConnectAsync("example.com", 443, true, 5000);
        
        // 3. Start the background thread
        task.Run();
        
        Console.WriteLine(".NET Framework: Task is running...");
    }

    // --- Event Handler ---
    private void Socket_OnTaskCompleted(object sender, Chilkat.TaskCompletedEventArgs args)
    {
        // 4. Extract the completed task directly from the EventArgs
        Chilkat.Task completedTask = args.Task;

        if (completedTask.TaskSuccess)
        {
            bool success = completedTask.GetResultBool();
            Console.WriteLine($".NET Framework: Connection successful: {success}");
        }
        else
        {
            Console.WriteLine($"Error: {completedTask.ResultErrorText}");
        }
    }
}

Option 2B: The .NET Core (and .NET 5+) Approach (Callbacks & Task Tracking)

In .NET Core, you must instantiate a specific Chilkat delegate and pass it to a setter method (like setTaskCompletedCb). Because the callback only provides an integer taskId rather than the task object itself, you must store the Chilkat.Task in a collection before you run it so you can look it up when the callback fires.

using System;
using System.Collections.Concurrent;

public class ChilkatCoreExample
{
    // 1. Maintain a dictionary to hold references to running tasks.
    // We use ConcurrentDictionary because the callback happens on a background thread.
    private ConcurrentDictionary<int, Chilkat.Task> _runningTasks = new ConcurrentDictionary<int, Chilkat.Task>();

    public void ConnectAsync()
    {
        Chilkat.Socket socket = new Chilkat.Socket();

        // 2. Instantiate the specific delegate and set the callback
        Chilkat.Socket.TaskCompleted taskCallback = new Chilkat.Socket.TaskCompleted(Socket_TaskCompletedCallback);
        socket.setTaskCompletedCb(taskCallback);

        // 3. Create the task
        Chilkat.Task task = socket.ConnectAsync("example.com", 443, true, 5000);
        
        // 4. IMPORTANT: Store the task in the dictionary using its TaskId BEFORE running it
        _runningTasks[task.TaskId] = task;

        // 5. Start the background thread
        task.Run();

        Console.WriteLine(".NET Core: Task is running...");
    }

    // --- Callback Method ---
    // 6. The callback receives an integer taskId, NOT the Task object itself
    private void Socket_TaskCompletedCallback(int taskId)
    {
        // 7. Retrieve the task from the dictionary (and remove it to prevent memory leaks)
        if (_runningTasks.TryRemove(taskId, out Chilkat.Task completedTask))
        {
            if (completedTask.TaskSuccess)
            {
                // 8. Harvest the result
                bool success = completedTask.GetResultBool();
                Console.WriteLine($".NET Core: Connection successful: {success}");
            }
            else
            {
                Console.WriteLine($"Error: {completedTask.ResultErrorText}");
            }
        }
        else
        {
            Console.WriteLine($"Warning: TaskId {taskId} completed, but was not found in the dictionary.");
        }
    }
}

️ Crucial Warning for Both Frameworks (UI Threads)

Whether you are using the .NET Framework event or the .NET Core callback, the completion method will always execute on a Chilkat-managed background thread. If your application has a User Interface (WinForms, WPF, MAUI), you cannot update UI elements directly from inside Socket_OnTaskCompleted or Socket_TaskCompletedCallback. Attempting to do so will result in a Cross-Thread Exception. You must marshal the call back to the main UI thread using Invoke (WinForms) or Dispatcher.Invoke (WPF).


Pro-Tip: Bridging Chilkat Async with Modern C# async/await

Because Chilkat’s async model was designed to be universal across many languages, it can feel a bit archaic in modern C# compared to standard async/await.

Most modern .NET developers opt to ignore Chilkat's internal Task architecture entirely. Instead, they take Chilkat's standard synchronous methods and wrap them in C#'s native Task.Run(). This allows you to use native await seamlessly, avoiding event subscription and dictionary tracking entirely:

public async System.Threading.Tasks.Task<bool> ConnectToSocketModernAsync()
{
    Chilkat.Socket socket = new Chilkat.Socket();
    
    // Wrap the synchronous Chilkat method in a standard C# Task
    bool success = await System.Threading.Tasks.Task.Run(() => 
    {
        return socket.Connect("example.com", 443, true, 5000);
    });

    return success;
}

Summary: Chilkat's Async model uses the Chilkat.Task object to provide background execution natively from its C++ core. You generate a task, run it, wait for it (via polling, Framework events, or Core callbacks), and manually extract the typed result. However, in C#, you are entirely free to bypass this architecture and wrap Chilkat's synchronous methods in .NET's native Task.Run() for a cleaner, more modern async/await experience.