Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel a Task executing a non managed C++ external routine

I'm trying to fix C# async code launching a cancelable operation performed in an external dll written in unmanaged C++ routine.

Is there a way to cancel a Task using a Cancellation Token passed to the Task at creation, if the user delegate calls an external non managed C++ routine ?

As far I know, Task cancellation involves cooperation between the user delegate and the code that requested the cancellation. A successful cancellation involves the requesting code calling the CancellationTokenSource.Cancel method, and the user delegate terminating the operation in a timely manner either by simply returning from the delegate when he notices that the a cancellation request has been raised (by polling the CancellationToken.IsCancellationRequested method) or by throwing an OperationCanceledException using CancellationToken.ThrowIfCancellationRequested method. (cf http://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx)

These two ways involve that the non managed C++ routine executed by the user delegate cooperates by receiving the CancellationToken as a parameter and by calling at regular intervals its IsCancellationRequested and/or ThrowIfCancellationRequested methods.

Is that possible to do that from a non managed external C++ routine ?

If not, is there a way to force the termination of the task executing the user delegate (executing the non managed c++ routine) when the cancellation is requested by the requesting code ?

Here is an example (extract) of the mixed C# / C++Cli / Unmanaged C++ code I'm trying to fix in order to be able to cancel the operation performed by the user delegate in the C++ unmanaged code part:

FrmDemo.cs:-------------------------------------------------------------------------

public class FrmDemo : Form
{
    private CliClass m_CliObject;
    private System.Threading.CancellationTokenSource m_Cts;
    private System.Threading.CancellationToken m_Ct;

    private void FrmDemo_Load(object sender, EventArgs e)
    {
        // Creating the external CliObject:
        this.m_CliObject = new NSDemo.CliClass();
        ...
    }

    // Event handler of the button starting the cancelable asynchrone operation:
    private async void btnStart_Click(object sender, EventArgs e)
    {
        m_Cts = new System.Threading.CancellationTokenSource();
        m_Ct = m_Cts.Token;
        await Task.Factory.StartNew(() =>
        {
              // Launching a cancelable operation performed by a managed C++Cli Object :
              this.m_CliObject.DoSomething();   // How to eventually pass the CancellationToken m_ct to the m_CliObject ?
        }, m_ct);
        ...
    }


    //Event handler of the cancel button:
    private void btnCancel_Click(object sender, EventArgs e)
    {
        // Requesting cancellation:
        m_Cts.Cancel();
        // (Or alternatively, how to eventually force the termination of the async Task without collaboration from it ?)
    }

CliClass.h:-----------------------------------------------------

#include "DemoCore.h"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace cli;

namespace NSDemo
{
    public ref class CliClass
    {

    public:

        CliClass();

        ~CliClass(); 

        void DoSomething()
        {
            // Performing the operation in the unmanaged coreObject:
            _coreObject->DoSomething();
        }

    private:
        UNSDemo::CoreClass *_coreObject;
        bool _disposed;

    };
}

CliClass.cpp:------------------------------------------

namespace NSDemo
{
    CliClass::CliClass()
    {
         _coreObject = new UNSDemo::CoreClass(...);
        ....
    }

    CliClass::~CliClass()
    {
        if (_disposed)
            return;               
        if (_coreObject != nullptr) {
            delete _coreObject;
            _coreObject = nullptr;
        }
        _disposed = true;
        GC::SuppressFinalize(this);
    }

CoreClass.h-----------------------------------------------------------------

namespace UNSDemo {

    class __declspec(dllexport) CoreClass {
    public:
        ScanningCore();

        ~ScanningCore();

        void DoSomething();

    private:

    ...

    };

}

CoreClass.cpp:----------------------------------------------------------------------------

#include "CoreClass.h"

namespace UNSDemo {

    CoreClass::CoreClass()
    {
        ...
    }

    CoreClass::~CoreClass()
    {
        ...
    }

    // Method actually performing the cancelable operation:
    void CoreClass::DoSomething()
    {
        // Main loop of the unmanaged cancelable operation:
        while (...) {
            ...
            // How to check the cancellation request from here ? (How to access the CancellationToken ?)
            // and if cancellation is requested, how to eventually throw the OperationCanceledException ?

        }
    }
}

Thank you for any help.

like image 295
PF Culand Avatar asked Oct 30 '14 13:10

PF Culand


People also ask

How do I cancel async tasks?

You can cancel an asynchronous operation after a period of time by using the CancellationTokenSource. CancelAfter method if you don't want to wait for the operation to finish.


1 Answers

If you're dealing with purely unmanaged code, it doesn't know about the CancellationToken class, so you can't pass it around like you would with managed code.

What I would do is declare your unmanaged method to take a pointer to a Boolean, and have the unmanaged code abort itself if the boolean is set true. In your wrapper, use CancellationToken.Register to register a callback that will set the Boolean to true when the CancellationToken is cancelled.

This sounds easy on the surface, but it's a bit complex because you need a managed event handler that can access a Boolean value that you're allowed to take the address of.

public ref class CancelableTaskWrapper
{
private:
    bool* isCanceled;
    void (*unmanagedFunctionPointer)(bool*);

    void Canceled() { if (isCanceled != nullptr) *isCanceled = true; }

public:
    CancelableTaskWrapper(void (*unmanagedFunctionPointer)(bool*))
    {
        this->unmanagedFunctionPointer = unmanagedFunctionPointer;

        isCanceled = new bool;
    }

    ~CancelableTaskWrapper() { if (isCanceled != nullptr) delete isCanceled; isCanceled = nullptr; }
    !CancelableTaskWrapper() { if (isCanceled != nullptr) delete isCanceled; isCanceled = nullptr; }

    void RunTask(CancellationToken cancelToken)
    {
        *isCanceled = false;
        CancellationTokenRegistration reg = cancelToken.Register(
            gcnew Action(this, &CancelableTaskWrapper::Canceled));
        unmanagedFunctionPointer(isCanceled);
    }
};

void someUnmanagedFunction(bool* isCanceled)
{
    doSomethingLongRunning();
    if(*isCanceled) return;
    doSomethingLongRunning();
}
  • Because isCanceled is a pointer to a bool, it's on the heap. Therefore, we're allowed to pass a pointer to it without needing to do anything special (e.g, pinning the managed object).
  • CancellationTokenRegistration implements IDisposable, it will automatically unregister itself when reg goes out of scope. (You'd do this with a using statement in C#.)

Disclaimer: I'm not at a compiler right now; there may be syntax errors.

like image 154
David Yaw Avatar answered Oct 29 '22 17:10

David Yaw