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.
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.
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();
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With